Skip to content

Commit cf751cf

Browse files
pi-anldpgeorge
authored andcommitted
extmod/modlwip: Fix crash when calling recv on listening socket.
Add check to prevent calling recv on a socket in the listening state. This prevents a crash/hard fault when user code mistakenly tries to recv on the listening socket instead of on the accepted connection. Add corresponding test case to demonstrate the bug. Signed-off-by: Andrew Leech <[email protected]>
1 parent e57aa7e commit cf751cf

File tree

2 files changed

+55
-15
lines changed

2 files changed

+55
-15
lines changed

extmod/modlwip.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,12 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_
860860
}
861861
}
862862

863+
if (socket->state == STATE_LISTENING) {
864+
// original socket in listening state, not the accepted connection.
865+
*_errno = MP_ENOTCONN;
866+
return -1;
867+
}
868+
863869
MICROPY_PY_LWIP_ENTER
864870

865871
assert(socket->pcb.tcp != NULL);

tests/multi_net/tcp_accept_recv.py

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,64 @@
1-
# Test recv on socket that just accepted a connection
1+
# Test recv on listening socket after accept(), with various listen() arguments
22

33
import socket
44

55
PORT = 8000
66

7+
# Test cases for listen() function
8+
LISTEN_ARGS = [None, 0, 1, 2] # None means no argument
9+
710

811
# Server
912
def instance0():
1013
multitest.globals(IP=multitest.get_network_ip())
11-
s = socket.socket()
12-
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
13-
s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1])
14-
s.listen(1)
1514
multitest.next()
16-
s.accept()
17-
try:
18-
print("recv", s.recv(10)) # should raise Errno 107 ENOTCONN
19-
except OSError as er:
20-
print(er.errno in (107, 128))
21-
s.close()
15+
16+
for i, listen_arg in enumerate(LISTEN_ARGS):
17+
s = socket.socket()
18+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
19+
s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1])
20+
21+
# Call listen with or without argument based on test case
22+
if listen_arg is None:
23+
print(f"Test case {i + 1}/{len(LISTEN_ARGS)}: listen()")
24+
s.listen()
25+
else:
26+
print(f"Test case {i + 1}/{len(LISTEN_ARGS)}: listen({listen_arg})")
27+
s.listen(listen_arg)
28+
29+
# Signal client that server is ready
30+
multitest.broadcast(f"server_ready_{i}")
31+
32+
# Wait for client connection
33+
c, _ = s.accept()
34+
35+
try:
36+
print("recv", s.recv(10)) # should raise Errno 107 ENOTCONN
37+
except OSError as er:
38+
# Verify the error code is either 107 (ENOTCONN) or 128 (ENOTCONN on Windows)
39+
print(er.errno in (107, 128))
40+
41+
# Cleanup
42+
c.close()
43+
s.close()
44+
45+
# Signal client we're done with this test case
46+
multitest.broadcast(f"server_done_{i}")
2247

2348

2449
# Client
2550
def instance1():
2651
multitest.next()
27-
s = socket.socket()
28-
s.connect(socket.getaddrinfo(IP, PORT)[0][-1])
29-
s.send(b"GET / HTTP/1.0\r\n\r\n")
30-
s.close()
52+
53+
for i, _ in enumerate(LISTEN_ARGS):
54+
# Wait for server to be ready
55+
multitest.wait(f"server_ready_{i}")
56+
57+
# Connect to server
58+
s = socket.socket()
59+
s.connect(socket.getaddrinfo(IP, PORT)[0][-1])
60+
s.send(b"GET / HTTP/1.0\r\n\r\n")
61+
s.close()
62+
63+
# Wait for server to finish this test case
64+
multitest.wait(f"server_done_{i}")

0 commit comments

Comments
 (0)