8000 Socket ether linux step2 by devnexen · Pull Request #17926 · php/php-src · GitHub
[go: up one dir, main page]

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

10000

@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 16 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

@devnexen devnexen requested a review from arnaud-lb July 2, 2025 21:34
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'm starting to question the idea of parsing packets in C. It is very easy to make mistakes here, which would result in security issues, specially as AF_PACKET requires elevated privileges.

In a more security-conscious approach we would return the recvfrom() buffer as a string, and let userland parse it safely.

zend_update_property_string(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("dstMac"), ether_ntoa((struct ether_addr *)innere.h_dest));
zend_update_property_long(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("headerSize"), ETH_HLEN);
zend_update_property(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("socket"), socket);
zend_update_property_stringl(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("rawPacket"), (char *)ether_hdr_buf.buf, ether_hdr_buf.chunk_len);
Copy link
Member

Choose a reason for hiding this comment

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

Should be .buf_len, not .chunk_len, otherwise this exposes only the header, not the raw packet

zval *addr = arg5;
zval *index = arg6;
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");
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
zend_argument_value_error(4, "must set one the flags MSG_PEEK, MSG_DONTWAIT, MSG_ERRQUEUE");
zend_argument_value_error(4, "contains invalid flags. It must be zero or more of MSG_PEEK, MSG_DONTWAIT, MSG_ERRQUEUE");

Copy link
Member Author
@devnexen devnexen Jul 6, 2025

Choose a reason for hiding this comment

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

I think the first part is redundant, the exception already mentions the argument name which is $flags.

struct ipv6hdr ip;
memcpy(&ip, ether_hdr_buf.buf, sizeof(ip));
size_t totalip = sizeof(ip) + ip.payload_len;
if (totalip < slen) {
Copy link
Member

Choose a reason for hiding this comment

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

We should not use slen (the size of struct sockaddr_ll) here, and I think the comparison is inversed:

Suggested change
if (totalip < slen) {
size_t totalip = sizeof(ip) + ip.payload_len;
if (totalip > ether_hdr_buf.buf_len) {

Comment on lines +1835 to +1836
zend_string_efree(recv_buf);
zend_value_error("invalid transport header length");
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
zend_string_efree(recv_buf);
zend_value_error("invalid transport header length");
zend_value_error("invalid IPv6 payload len");

zend_update_property_string(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("srcAddr"), inet_ntoa(s));
zend_update_property_string(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("dstAddr"), inet_ntoa(d));
zend_update_property_long(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("headerSize"), totalip);
zend_update_property_stringl(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("rawPacket"), (char *)ether_hdr_buf.buf, ether_hdr_buf.chunk_len);
Copy link
Member

Choose a reason for hiding this comment

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

ether_hdr_buf is not the raw packet, as it starts after the IP header

Copy link
Member Author

Choose a reason for hiding this comment

The reason wil B513 l be displayed to describe this comment to others. Learn more.

so is the whole raw buffer from start ? if yes we already store it in the "root" part.

Copy link
Member

Choose a reason for hiding this comment

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

The raw IP packet starts at the beginning of the IP header. You call php_socket_get_chunk() two times in case ETH_P_IP:: The first one gives you a chunk that starts at the right position for rawPacket. You probably need a separate chunk variable for the second call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0