Skip to content

Socket ether linux step2 #17926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

devnexen
Copy link
Member

No description provided.

@devnexen devnexen force-pushed the socket_ether_linux_step2 branch 2 times, most recently from e201885 to 1f1f204 Compare February 26, 2025 21:09
@devnexen devnexen marked this pull request as ready for review February 26, 2025 21:46
@devnexen devnexen requested a review from kocsismate as a code owner February 26, 2025 21:46
@devnexen devnexen requested a review from arnaud-lb February 26, 2025 21:46
@devnexen devnexen marked this pull request as draft February 27, 2025 12:16
@devnexen devnexen marked this pull request as ready for review February 27, 2025 21:28
@bukka
Copy link
Member

bukka commented Mar 3, 2025

This has a bit of class design so it should be probably at least posted to internals (it's not like adding a constant or a simple funciton). I agree that there should be some namespacing as well possibly. Definitely not the current names that will easily result in BC break for appliations.

@devnexen devnexen marked this pull request as draft March 7, 2025 19:52
@devnexen devnexen force-pushed the socket_ether_linux_step2 branch 2 times, most recently from 9e8daaa to 8cd42ca Compare March 7, 2025 22:29
@devnexen devnexen marked this pull request as ready for review March 8, 2025 18:14
@devnexen
Copy link
Member Author

ping @arnaud-lb I think I have, at least, addressed the majority of your remarks :).

@devnexen devnexen force-pushed the socket_ether_linux_step2 branch 2 times, most recently from c2fa112 to 3a67b50 Compare March 20, 2025 04:18
Copy link
Member

@arnaud-lb arnaud-lb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay, I missed you ping

@devnexen devnexen force-pushed the socket_ether_linux_step2 branch 7 times, most recently from f8cb8fe to d23895d Compare April 13, 2025 13:05
@devnexen devnexen requested a review from arnaud-lb April 13, 2025 13:45
@devnexen devnexen force-pushed the socket_ether_linux_step2 branch 17 times, most recently from 7aed82a to 57066bc Compare June 30, 2025 11:04
Copy link
Member

@arnaud-lb arnaud-lb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found no more issues apart from a few comments.

This feels safe as all accesses to the read buffer are guarded by php_socket_get_chunk(). However, we often use this function only for checking the length, and we copy the buffer again after calling it. Maybe the function could be changed to something like this, instead:

typedef struct _php_socket_chunk {
    const char *c;
    size_t chunk_len; /* length of this chunk */
    size_t buf_len; /* length of this chunk plus everything after the chunk in c */
} php_socket_chunk;

static zend_result php_socket_get_chunk(php_socket_chunk *chunk, const php_socket_chunk *src, size_t offset, size_t len) {
	if (UNEXPECTED((offset > SIZE_MAX - len) || offset + len > src->buf_len)) {
		return FAILURE;
	}

        dest->c = src->c + offset;
        dest->chunk_len = len;
        dest->buf_len = src->buf_len - offset;
	return SUCCESS;
}

php_socket_chunk *chunk = {
    .s = ZSTR_VAL(recv_buf),
    .buf_len = ZSTR_LEN(recv_buf),
};

if (php_socket_get_chunk(chunk, chunk, 0, ETH_HLEN) == FAILURE) {
    ...
}

struct ethhdr e;
memcpy(&e, ethhdr_chunk->c, ETH_HLEN);

if (php_socket_get_chunk(chunk, chunk, 0, sizeof(struct iphdr)) == FAILURE) {
    ...
}

struct iphdr ip;
memcpy(&ip, ethhdr_chunk->c, sizeof(ip));
ZSTR_LEN(recv_buf) = retval;
ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0';

if (UNEXPECTED(slen < ETH_HLEN)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be replaced by a ZEND_ASSERT(), as both slen and ETH_HLEN are compile time constants.


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), recv_len, recv_flags, (struct sockaddr *)&sll, (socklen_t *)&slen);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With recv_flags & MSG_TRUNC, it is possible that retval is longer than ZSTR_LEN(recv_buf).

I suggest that we allow only a finite set of known and supported flags in recv_flags.


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), recv_len, recv_flags, (struct sockaddr *)&sll, (socklen_t *)&slen);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, ZSTR_LEN(recv_buf) may be larger than retval. You need to update ZSTR_LEN(recv_buf) after this call, otherwise you expose uninitialized memory past the packet data:

$s_c     = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL);
$s_bind  = socket_bind($s_c, 'lo');
$s_s     = socket_create(AF_PACKET, SOCK_RAW, ETH_P_LOOP);
$v_bind  = socket_bind($s_s, 'lo');

$buf = pack("H12H12n", "ffffffffffff", "000000000000", ETH_P_LOOP);
$buf .= str_repeat("A", 46);

printf("buf len: %d\n", strlen($buf));

socket_sendto($s_s, $buf, strlen($buf), 0, "lo", 1);
socket_recvfrom($s_c, $rsp, strlen($buf)+1000, 0, $addr);

var_dump(bin2hex($rsp->rawPacket));
@@ -1416,6 +1435,57 @@ PHP_FUNCTION(socket_bind)
}
/* }}} */

#ifdef AF_PACKET
#define ETH_SUB_CHECKLENGTH(a, lyr) \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This macro is unused

Comment on lines 1459 to 1460
if ((char *)ipdata + sizeof(tcp) < ZSTR_VAL(recv_buf) + slen)
return FAILURE;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this check. Is it necessary, when the length of ipdata is already checked by php_socket_get_chunk()? Same remark for php_socket_afpacket_add_udp().

@devnexen devnexen force-pushed the socket_ether_linux_step2 branch 2 times, most recently from a7b421f to 40850b2 Compare July 1, 2025 17:18
Back to the drawing board due to UAF with previous version.

This reverts commit cc11606.
@devnexen devnexen force-pushed the socket_ether_linux_step2 branch from 40850b2 to 423bfb7 Compare July 1, 2025 18:28
@devnexen devnexen marked this pull request as ready for review July 1, 2025 19:26
@devnexen devnexen requested a review from arnaud-lb July 1, 2025 19:26
Comment on lines 1699 to 1703
if (recv_flags > 0 && !(recv_flags & (MSG_PEEK|MSG_DONTWAIT|MSG_ERRQUEUE))) {
zend_argument_value_error(4, "must set one the flags MSG_PEEK, MSG_DONTWAIT, MSG_ERRQUEUE");
zend_string_efree(recv_buf);
RETURN_THROWS();
}
Copy link
Member

@arnaud-lb arnaud-lb Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows to pass any flag as long as recv_flags contains one of MSG_PEEK|MSG_DONTWAIT|MSG_ERRQUEUE. You probably want something like if (recv_flags & ~(MSG_PEEK|MSG_DONTWAIT|MSG_ERRQUEUE)) { error }.


switch (protocol) {
case ETH_P_IP: {
if (php_socket_get_chunk(&ether_hdr_buf, &raw_buf, 0, sizeof(struct iphdr)) == FAILURE) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, but I think you wanted to set offset to ETH_HLEN here.

Copy link
Member Author

@devnexen devnexen Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes that explain and the code typo issue below some tests inconsistencies

Comment on lines 245 to 261
object(Socket\EthernetPacket)#3 (%d) {
["headerSize"]=>
int(%d)
["rawPacket"]=>
string(%d) "%r(.|\n)*?"%r
["socket"]=>
object(Socket)#1 (0) {
}
["ethProtocol"]=>
int(%i)
["srcMac"]=>
string(%d) "%s:%s:%s:%s:%s:%s"
["dstMac"]=>
string(%d) "%s:%s:%s:%s:%s:%s"
["payload"]=>
%a
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since most of these fields have a fixed/deterministic value, I suggest to check their exact value (without % placeholders). Including the lengths (e.g. string(%d) -> string(42)).

For binary fields such as rawPacket and payload, keep using a placeholder, but also dump them separately in hex.

Same remark for the entire test and other tests.

@devnexen devnexen requested a review from arnaud-lb July 2, 2025 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment