Skip to content

Commit dd7888a

Browse files
committed
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 dd7888a

File tree

2 files changed

+64
-15
lines changed

2 files changed

+64
-15
lines changed

extmod/modlwip.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,12 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_
825825
// Check for any pending errors
826826
STREAM_ERROR_CHECK(socket);
827827

828+
if (socket->state == STATE_LISTENING) {
829+
// original socket in listening state, not the accepted connection.
830+
*_errno = MP_ENOTCONN;
831+
return -1;
832+
}
833+
828834
if (socket->incoming.tcp.pbuf == NULL) {
829835

830836
// Non-blocking socket or flag

tests/multi_net/tcp_accept_recv.py

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,73 @@
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+
test_num = 0
17+
for blocking_mode in [True, False]:
18+
for listen_arg in LISTEN_ARGS:
19+
test_num += 1
20+
s = socket.socket()
21+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
22+
s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1])
23+
24+
# Call listen with or without argument based on test case
25+
if listen_arg is None:
26+
print(f"Test case {test_num}/8: listen() blocking={blocking_mode}")
27+
s.listen()
28+
else:
29+
print(f"Test case {test_num}/8: listen({listen_arg}) blocking={blocking_mode}")
30+
s.listen(listen_arg)
31+
32+
# Signal client that server is ready
33+
multitest.broadcast(f"server_ready_{test_num}")
34+
35+
# Wait for client connection
36+
c, _ = s.accept()
37+
38+
# Set blocking mode after accept
39+
s.setblocking(blocking_mode)
40+
41+
try:
42+
print("recv", s.recv(10)) # should raise Errno 107 ENOTCONN
43+
except OSError as er:
44+
# Verify the error code is either 107 (ENOTCONN) or 128 (ENOTCONN on Windows)
45+
print(er.errno in (107, 128))
46+
47+
# Cleanup
48+
c.close()
49+
s.close()
50+
51+
# Signal client we're done with this test case
52+
multitest.broadcast(f"server_done_{test_num}")
2253

2354

2455
# Client
2556
def instance1():
2657
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()
58+
59+
test_num = 0
60+
for blocking_mode in [True, False]:
61+
for _ in LISTEN_ARGS:
62+
test_num += 1
63+
# Wait for server to be ready
64+
multitest.wait(f"server_ready_{test_num}")
65+
66+
# Connect to server
67+
s = socket.socket()
68+
s.connect(socket.getaddrinfo(IP, PORT)[0][-1])
69+
s.send(b"GET / HTTP/1.0\r\n\r\n")
70+
s.close()
71+
72+
# Wait for server to finish this test case
73+
multitest.wait(f"server_done_{test_num}")

0 commit comments

Comments
 (0)