-
Notifications
You must be signed in to change notification settings - Fork 7.9k
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
base: master
Are you sure you want to change the base?
Socket ether linux step2 #17926
Conversation
e201885
to
1f1f204
Compare
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. |
9e8daaa
to
8cd42ca
Compare
ping @arnaud-lb I think I have, at least, addressed the majority of your remarks :). |
c2fa112
to
3a67b50
Compare
There was a problem hiding this 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
f8cb8fe
to
d23895d
Compare
7aed82a
to
57066bc
Compare
There was a problem hiding this 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));
ext/sockets/sockets.c
Outdated
ZSTR_LEN(recv_buf) = retval; | ||
ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0'; | ||
|
||
if (UNEXPECTED(slen < ETH_HLEN)) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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));
ext/sockets/sockets.c
Outdated
@@ -1416,6 +1435,57 @@ PHP_FUNCTION(socket_bind) | |||
} | |||
/* }}} */ | |||
|
|||
#ifdef AF_PACKET | |||
#define ETH_SUB_CHECKLENGTH(a, lyr) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This macro is unused
ext/sockets/sockets.c
Outdated
if ((char *)ipdata + sizeof(tcp) < ZSTR_VAL(recv_buf) + slen) | ||
return FAILURE; |
There was a problem hiding this comment.
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()
.
a7b421f
to
40850b2
Compare
Back to the drawing board due to UAF with previous version. This reverts commit cc11606.
40850b2
to
423bfb7
Compare
ext/sockets/sockets.c
Outdated
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(); | ||
} |
There was a problem hiding this comment.
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 }
.
ext/sockets/sockets.c
Outdated
|
||
switch (protocol) { | ||
case ETH_P_IP: { | ||
if (php_socket_get_chunk(ðer_hdr_buf, &raw_buf, 0, sizeof(struct iphdr)) == FAILURE) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
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 | ||
} |
There was a problem hiding this comment.
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.
No description provided.