Skip to content

Commit c1a6258

Browse files
committed
ext/sockets: Enable AF_PACKET raw buffer support in socket_sendto/socket_recvfrom.
Take a new approach from PR php#17926: instead of parsing ethernet/IP/TCP/UDP headers in C, expose the raw frame as a string to userland, letting users handle protocol decoding safely in PHP. This addresses the security concerns raised during review. Also rename opaque argument variables (arg1..arg6) to meaningful names in both functions and fix a bug in the commented-out sendto code that was using &sin instead of &sll. close phpGH-21631
1 parent 9060fd3 commit c1a6258

6 files changed

Lines changed: 557 additions & 56 deletions

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@ PHP NEWS
233233
AF_INET* family only. (David Carlier)
234234
. Fixed GH-20532 (socket_addrinfo_lookup gives the error code with a new
235235
optional parameter). (David Carlier)
236+
. Added AF_PACKET support completion for socket_sendto()/socket_recvfrom().
237+
(David Carlier)
236238

237239
- Sodium:
238240
. Added support for libsodium 1.0.21 IPcrypt and XOF APIs. (jedisct1)

ext/sockets/sockets.c

Lines changed: 91 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,71 +1492,104 @@ PHP_FUNCTION(socket_send)
14921492
/* {{{ Receives data from a socket, connected or not */
14931493
PHP_FUNCTION(socket_recvfrom)
14941494
{
1495-
zval *arg1, *arg2, *arg5, *arg6 = NULL;
1495+
zval *zsocket, *zdata, *zaddr, *zport = NULL;
14961496
php_socket *php_sock;
14971497
struct sockaddr_un s_un;
14981498
struct sockaddr_in sin;
14991499
#ifdef HAVE_IPV6
15001500
struct sockaddr_in6 sin6;
15011501
#endif
15021502
#ifdef AF_PACKET
1503-
//struct sockaddr_ll sll;
1503+
struct sockaddr_ll sll;
15041504
#endif
15051505
char addrbuf[INET6_ADDRSTRLEN];
15061506
socklen_t slen;
15071507
int retval;
1508-
zend_long arg3, arg4;
1508+
zend_long length, flags;
15091509
const char *address;
15101510
zend_string *recv_buf;
15111511

15121512
ZEND_PARSE_PARAMETERS_START(5, 6)
1513-
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
1514-
Z_PARAM_ZVAL(arg2)
1515-
Z_PARAM_LONG(arg3)
1516-
Z_PARAM_LONG(arg4)
1517-
Z_PARAM_ZVAL(arg5)
1513+
Z_PARAM_OBJECT_OF_CLASS(zsocket, socket_ce)
1514+
Z_PARAM_ZVAL(zdata)
1515+
Z_PARAM_LONG(length)
1516+
Z_PARAM_LONG(flags)
1517+
Z_PARAM_ZVAL(zaddr)
15181518
Z_PARAM_OPTIONAL
1519-
Z_PARAM_ZVAL(arg6)
1519+
Z_PARAM_ZVAL(zport)
15201520
ZEND_PARSE_PARAMETERS_END();
15211521

1522-
php_sock = Z_SOCKET_P(arg1);
1522+
php_sock = Z_SOCKET_P(zsocket);
15231523
ENSURE_SOCKET_VALID(php_sock);
15241524

1525+
#ifdef AF_PACKET
1526+
/* On packet sockets, restrict flags to a finite safe subset. In
1527+
* particular MSG_TRUNC must be excluded: it makes recvfrom() report the
1528+
* untruncated frame length, which can exceed the buffer. */
1529+
if (php_sock->type == AF_PACKET) {
1530+
const zend_long allowed_flags = 0
1531+
#ifdef MSG_OOB
1532+
| MSG_OOB
1533+
#endif
1534+
#ifdef MSG_PEEK
1535+
| MSG_PEEK
1536+
#endif
1537+
#ifdef MSG_WAITALL
1538+
| MSG_WAITALL
1539+
#endif
1540+
#ifdef MSG_DONTWAIT
1541+
| MSG_DONTWAIT
1542+
#endif
1543+
#ifdef MSG_ERRQUEUE
1544+
| MSG_ERRQUEUE
1545+
#endif
1546+
#ifdef MSG_CMSG_CLOEXEC
1547+
| MSG_CMSG_CLOEXEC
1548+
#endif
1549+
;
1550+
1551+
if (flags & ~allowed_flags) {
1552+
zend_argument_value_error(4, "must be a combination of MSG_OOB, MSG_PEEK, MSG_WAITALL, MSG_DONTWAIT, MSG_ERRQUEUE, and MSG_CMSG_CLOEXEC for AF_PACKET sockets");
1553+
RETURN_THROWS();
1554+
}
1555+
}
1556+
#endif
1557+
15251558
/* overflow check */
15261559
/* Shouldthrow ? */
15271560

1528-
if (arg3 <= 0 || arg3 > ZEND_LONG_MAX - 1) {
1561+
if (length <= 0 || length > ZEND_LONG_MAX - 1) {
15291562
RETURN_FALSE;
15301563
}
15311564

1532-
recv_buf = zend_string_alloc(arg3 + 1, 0);
1565+
recv_buf = zend_string_alloc(length + 1, 0);
15331566

15341567
switch (php_sock->type) {
15351568
case AF_UNIX:
15361569
slen = sizeof(s_un);
15371570
memset(&s_un, 0, slen);
15381571
s_un.sun_family = AF_UNIX;
15391572

1540-
retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&s_un, (socklen_t *)&slen);
1573+
retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), length, flags, (struct sockaddr *)&s_un, (socklen_t *)&slen);
15411574

15421575
if (retval < 0) {
15431576
PHP_SOCKET_ERROR(php_sock, "Unable to recvfrom", errno);
15441577
zend_string_efree(recv_buf);
15451578
RETURN_FALSE;
15461579
}
1547-
ZSTR_LEN(recv_buf) = retval;
1580+
ZSTR_LEN(recv_buf) = MIN((size_t)retval, (size_t)length);
15481581
ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0';
15491582

1550-
ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf);
1551-
ZEND_TRY_ASSIGN_REF_STRING(arg5, s_un.sun_path);
1583+
ZEND_TRY_ASSIGN_REF_NEW_STR(zdata, recv_buf);
1584+
ZEND_TRY_ASSIGN_REF_STRING(zaddr, s_un.sun_path);
15521585
break;
15531586

15541587
case AF_INET:
15551588
slen = sizeof(sin);
15561589
memset(&sin, 0, slen);
15571590
sin.sin_family = AF_INET;
15581591

1559-
if (arg6 == NULL) {
1592+
if (zport == NULL) {
15601593
zend_string_efree(recv_buf);
15611594
zend_throw_exception(
15621595
zend_ce_argument_count_error,
@@ -1565,29 +1598,29 @@ PHP_FUNCTION(socket_recvfrom)
15651598
RETURN_THROWS();
15661599
}
15671600

1568-
retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sin, (socklen_t *)&slen);
1601+
retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), length, flags, (struct sockaddr *)&sin, (socklen_t *)&slen);
15691602

15701603
if (retval < 0) {
15711604
PHP_SOCKET_ERROR(php_sock, "Unable to recvfrom", errno);
15721605
zend_string_efree(recv_buf);
15731606
RETURN_FALSE;
15741607
}
1575-
ZSTR_LEN(recv_buf) = retval;
1608+
ZSTR_LEN(recv_buf) = MIN((size_t)retval, (size_t)length);
15761609
ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0';
15771610

15781611
address = inet_ntop(AF_INET, &sin.sin_addr, addrbuf, sizeof(addrbuf));
15791612

1580-
ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf);
1581-
ZEND_TRY_ASSIGN_REF_STRING(arg5, address ? address : "0.0.0.0");
1582-
ZEND_TRY_ASSIGN_REF_LONG(arg6, ntohs(sin.sin_port));
1613+
ZEND_TRY_ASSIGN_REF_NEW_STR(zdata, recv_buf);
1614+
ZEND_TRY_ASSIGN_REF_STRING(zaddr, address ? address : "0.0.0.0");
1615+
ZEND_TRY_ASSIGN_REF_LONG(zport, ntohs(sin.sin_port));
15831616
break;
15841617
#ifdef HAVE_IPV6
15851618
case AF_INET6:
15861619
slen = sizeof(sin6);
15871620
memset(&sin6, 0, slen);
15881621
sin6.sin6_family = AF_INET6;
15891622

1590-
if (arg6 == NULL) {
1623+
if (zport == NULL) {
15911624
zend_string_efree(recv_buf);
15921625
zend_throw_exception(
15931626
zend_ce_argument_count_error,
@@ -1596,41 +1629,38 @@ PHP_FUNCTION(socket_recvfrom)
15961629
RETURN_THROWS();
15971630
}
15981631

1599-
retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sin6, (socklen_t *)&slen);
1632+
retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), length, flags, (struct sockaddr *)&sin6, (socklen_t *)&slen);
16001633

16011634
if (retval < 0) {
16021635
PHP_SOCKET_ERROR(php_sock, "unable to recvfrom", errno);
16031636
zend_string_efree(recv_buf);
16041637
RETURN_FALSE;
16051638
}
1606-
ZSTR_LEN(recv_buf) = retval;
1639+
ZSTR_LEN(recv_buf) = MIN((size_t)retval, (size_t)length);
16071640
ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0';
16081641

16091642
inet_ntop(AF_INET6, &sin6.sin6_addr, addrbuf, sizeof(addrbuf));
16101643

1611-
ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf);
1612-
ZEND_TRY_ASSIGN_REF_STRING(arg5, addrbuf[0] ? addrbuf : "::");
1613-
ZEND_TRY_ASSIGN_REF_LONG(arg6, ntohs(sin6.sin6_port));
1644+
ZEND_TRY_ASSIGN_REF_NEW_STR(zdata, recv_buf);
1645+
ZEND_TRY_ASSIGN_REF_STRING(zaddr, addrbuf[0] ? addrbuf : "::");
1646+
ZEND_TRY_ASSIGN_REF_LONG(zport, ntohs(sin6.sin6_port));
16141647
break;
16151648
#endif
16161649
#ifdef AF_PACKET
1617-
/*
1618-
case AF_PACKET:
1619-
// TODO expose and use proper ethernet frame type instead i.e. src mac, dst mac and payload to userland
1620-
// ditto for socket_sendto
1621-
slen = sizeof(sll);
1622-
memset(&sll, 0, sizeof(sll));
1623-
sll.sll_family = AF_PACKET;
1650+
case AF_PACKET: {
16241651
char ifrname[IFNAMSIZ];
16251652

1626-
retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sll, (socklen_t *)&slen);
1653+
slen = sizeof(sll);
1654+
memset(&sll, 0, slen);
1655+
1656+
retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), length, flags, (struct sockaddr *)&sll, (socklen_t *)&slen);
16271657

16281658
if (retval < 0) {
16291659
PHP_SOCKET_ERROR(php_sock, "unable to recvfrom", errno);
16301660
zend_string_efree(recv_buf);
16311661
RETURN_FALSE;
16321662
}
1633-
ZSTR_LEN(recv_buf) = retval;
1663+
ZSTR_LEN(recv_buf) = MIN((size_t)retval, (size_t)length);
16341664
ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0';
16351665

16361666
if (UNEXPECTED(!if_indextoname(sll.sll_ifindex, ifrname))) {
@@ -1639,15 +1669,17 @@ PHP_FUNCTION(socket_recvfrom)
16391669
RETURN_FALSE;
16401670
}
16411671

1642-
ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf);
1643-
ZEND_TRY_ASSIGN_REF_STRING(arg5, ifrname);
1644-
ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex);
1672+
ZEND_TRY_ASSIGN_REF_NEW_STR(zdata, recv_buf);
1673+
ZEND_TRY_ASSIGN_REF_STRING(zaddr, ifrname);
1674+
1675+
if (zport) {
1676+
ZEND_TRY_ASSIGN_REF_LONG(zport, sll.sll_ifindex);
1677+
}
16451678
break;
1646-
*/
1679+
}
16471680
#endif
16481681
default:
1649-
zend_string_efree(recv_buf);
1650-
zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6");
1682+
zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_INET, or AF_INET6");
16511683
RETURN_THROWS();
16521684
}
16531685

@@ -1658,15 +1690,15 @@ PHP_FUNCTION(socket_recvfrom)
16581690
/* {{{ Sends a message to a socket, whether it is connected or not */
16591691
PHP_FUNCTION(socket_sendto)
16601692
{
1661-
zval *arg1;
1693+
zval *zsocket;
16621694
php_socket *php_sock;
16631695
struct sockaddr_un s_un;
16641696
struct sockaddr_in sin;
16651697
#ifdef HAVE_IPV6
16661698
struct sockaddr_in6 sin6;
16671699
#endif
16681700
#ifdef AF_PACKET
1669-
//struct sockaddr_ll sll;
1701+
struct sockaddr_ll sll;
16701702
#endif
16711703
int retval;
16721704
size_t buf_len;
@@ -1676,7 +1708,7 @@ PHP_FUNCTION(socket_sendto)
16761708
zend_string *addr;
16771709

16781710
ZEND_PARSE_PARAMETERS_START(5, 6)
1679-
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
1711+
Z_PARAM_OBJECT_OF_CLASS(zsocket, socket_ce)
16801712
Z_PARAM_STRING(buf, buf_len)
16811713
Z_PARAM_LONG(len)
16821714
Z_PARAM_LONG(flags)
@@ -1685,14 +1717,19 @@ PHP_FUNCTION(socket_sendto)
16851717
Z_PARAM_LONG_OR_NULL(port, port_is_null)
16861718
ZEND_PARSE_PARAMETERS_END();
16871719

1688-
php_sock = Z_SOCKET_P(arg1);
1720+
php_sock = Z_SOCKET_P(zsocket);
16891721
ENSURE_SOCKET_VALID(php_sock);
16901722

1691-
if (port < 0 || port > USHRT_MAX) {
1692-
zend_argument_value_error(6, "must be between 0 and %u", USHRT_MAX);
1693-
RETURN_THROWS();
1723+
#ifdef AF_PACKET
1724+
if (php_sock->type != AF_PACKET) {
1725+
#endif
1726+
if (port < 0 || port > USHRT_MAX) {
1727+
zend_argument_value_error(6, "must be between 0 and %u", USHRT_MAX);
1728+
RETURN_THROWS();
1729+
}
1730+
#ifdef AF_PACKET
16941731
}
1695-
1732+
#endif
16961733

16971734
if (len < 0) {
16981735
zend_argument_value_error(3, "must be greater than or equal to 0");
@@ -1749,7 +1786,6 @@ PHP_FUNCTION(socket_sendto)
17491786
break;
17501787
#endif
17511788
#ifdef AF_PACKET
1752-
/*
17531789
case AF_PACKET:
17541790
if (port_is_null) {
17551791
zend_argument_value_error(6, "cannot be null when the socket type is AF_PACKET");
@@ -1758,14 +1794,13 @@ PHP_FUNCTION(socket_sendto)
17581794

17591795
memset(&sll, 0, sizeof(sll));
17601796
sll.sll_family = AF_PACKET;
1761-
sll.sll_ifindex = port;
1797+
sll.sll_ifindex = (int)port;
17621798

1763-
retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *) &sin, sizeof(sin));
1799+
retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *)&sll, sizeof(sll));
17641800
break;
1765-
*/
17661801
#endif
17671802
default:
1768-
zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6");
1803+
zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_INET, or AF_INET6");
17691804
RETURN_THROWS();
17701805
}
17711806

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
AF_PACKET socket_recvfrom() without optional port argument
3+
--EXTENSIONS--
4+
sockets
5+
posix
6+
--SKIPIF--
7+
<?php
8+
if (!defined("AF_PACKET")) {
9+
die('SKIP AF_PACKET not supported on this platform.');
10+
}
11+
if (!defined("ETH_P_ALL")) {
12+
die('SKIP ETH_P_ALL not available on this platform.');
13+
}
14+
if (!function_exists("posix_getuid") || posix_getuid() != 0) {
15+
die('SKIP AF_PACKET requires root permissions.');
16+
}
17+
?>
18+
--FILE--
19+
<?php
20+
$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL);
21+
$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL);
22+
23+
socket_bind($s_send, 'lo');
24+
socket_bind($s_recv, 'lo');
25+
26+
$dst_mac = "\xff\xff\xff\xff\xff\xff";
27+
$src_mac = "\x00\x00\x00\x00\x00\x00";
28+
$ethertype = pack("n", 0x9000);
29+
$payload = "no port test";
30+
$frame = str_pad($dst_mac . $src_mac . $ethertype . $payload, 60, "\x00");
31+
32+
socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1);
33+
34+
// recvfrom without the optional 6th argument (port/ifindex).
35+
$bytes = socket_recvfrom($s_recv, $buf, 65536, 0, $addr);
36+
var_dump($bytes >= 60);
37+
var_dump($addr === 'lo');
38+
39+
socket_close($s_send);
40+
socket_close($s_recv);
41+
?>
42+
--EXPECT--
43+
bool(true)
44+
bool(true)

0 commit comments

Comments
 (0)