Skip to content

Commit 32f160c

Browse files
osobiehlfabiobaltieri
authored andcommitted
net: coap: fix socket closing in another thread
Closing a socket in another thread can lead to use-after-free issues on `native_sim` when that socket is being used in epoll. This fix ensures that closing the socket is always done in the coap server thread, ensuring no race on the sockets occurs Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Jose Ignacio Biehl Ulate <joseignacio.biehlulate@draeger.com>
1 parent 9eea099 commit 32f160c

2 files changed

Lines changed: 68 additions & 7 deletions

File tree

include/zephyr/net/coap_service.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ extern "C" {
4949

5050
struct coap_service_data {
5151
int sock_fd;
52+
bool close_requested;
5253
struct coap_observer observers[CONFIG_COAP_SERVICE_OBSERVERS];
5354
struct coap_pending pending[CONFIG_COAP_SERVICE_PENDING_MESSAGES];
5455
#if defined(CONFIG_COAP_OSCORE) || defined(__DOXYGEN__)

subsys/net/lib/coap/coap_server.c

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ BUILD_ASSERT(CONFIG_ZVFS_POLL_MAX > 0, "CONFIG_ZVFS_POLL_MAX can't be 0");
6161
static K_MUTEX_DEFINE(lock);
6262
static int control_sock;
6363

64+
/* Signalled by the server thread once it has honoured a deferred close request
65+
* (see coap_service_stop() and the deferred-close pass in coap_server_thread()).
66+
*/
67+
static K_CONDVAR_DEFINE(close_done);
68+
/* Identity of the server (poll) thread, captured when it starts. Lets
69+
* coap_service_stop() detect a same-thread call and close directly instead of
70+
* deadlocking on itself.
71+
*/
72+
extern const k_tid_t coap_server_id;
73+
6474
#if defined(CONFIG_COAP_SERVER_PENDING_ALLOCATOR_STATIC)
6575
K_MEM_SLAB_DEFINE_STATIC(pending_data, COAP_SERVER_WIRE_MESSAGE_SIZE,
6676
CONFIG_COAP_SERVER_PENDING_ALLOCATOR_STATIC_BLOCKS, 4);
@@ -900,20 +910,44 @@ int coap_service_stop(const struct coap_service *service)
900910

901911
k_mutex_lock(&lock, K_FOREVER);
902912

903-
if (service->data->sock_fd < 0) {
913+
if ((service->data->sock_fd < 0) || (service->data->close_requested)) {
904914
k_mutex_unlock(&lock);
905915
return -EALREADY;
906916
}
907917

908-
/* Closing a socket will trigger a poll event */
909-
ret = zsock_close(service->data->sock_fd);
910-
service->data->sock_fd = -1;
918+
if (k_current_get() == coap_server_id) {
919+
/* Called from the server thread itself (e.g. a resource handler).
920+
* zsock_poll() has already returned,
921+
*/
922+
ret = zsock_close(service->data->sock_fd);
923+
service->data->sock_fd = -1;
924+
k_mutex_unlock(&lock);
925+
926+
coap_service_raise_event(service, NET_EVENT_COAP_SERVICE_STOPPED);
927+
928+
return ret;
929+
}
930+
931+
/* Defer the close to the server thread: it must remove the fd from its
932+
* poll set before the socket is closed. Closing here would close a socket
933+
* the server thread may hold inside an active zsock_poll(), which can lead
934+
* to socket fd reuse issues. Wake the poll, then block until the
935+
* server thread has performed the close, preserving the synchronous
936+
* "socket released on return" contract callers (e.g. stop-then-rebind)
937+
* rely on.
938+
*/
939+
service->data->close_requested = true;
940+
coap_server_update_services();
941+
942+
while (service->data->close_requested) {
943+
k_condvar_wait(&close_done, &lock, K_FOREVER);
944+
}
911945

912946
k_mutex_unlock(&lock);
913947

914948
coap_service_raise_event(service, NET_EVENT_COAP_SERVICE_STOPPED);
915949

916-
return ret;
950+
return 0;
917951
}
918952

919953
int coap_service_is_running(const struct coap_service *service)
@@ -1360,10 +1394,36 @@ static void coap_server_thread(void *p1, void *p2, void *p3)
13601394
k_msleep(10);
13611395
}
13621396

1397+
/* Honour deferred close requests from coap_service_stop(). zsock_poll()
1398+
* has returned, so every fd has already been removed from the poll set;
1399+
* closing here cannot race an active poll.
1400+
*/
1401+
k_mutex_lock(&lock, K_FOREVER);
1402+
COAP_SERVICE_FOREACH(svc) {
1403+
if (svc->data->close_requested && svc->data->sock_fd >= 0) {
1404+
/* invalidate the fd in the poll set */
1405+
for (int i = 0; i < sock_nfds; ++i) {
1406+
if (sock_fds[i].fd == svc->data->sock_fd) {
1407+
sock_fds[i].fd = -1;
1408+
break;
1409+
}
1410+
}
1411+
1412+
(void)zsock_close(svc->data->sock_fd);
1413+
svc->data->sock_fd = -1;
1414+
svc->data->close_requested = false;
1415+
}
1416+
}
1417+
k_mutex_unlock(&lock);
1418+
k_condvar_broadcast(&close_done);
1419+
13631420
for (int i = 0; i < sock_nfds; ++i) {
1421+
if (sock_fds[i].fd == -1) {
1422+
/* closing this fd was requested */
1423+
continue;
1424+
}
13641425
/* Check the wake up event */
1365-
if (sock_fds[i].fd == control_sock &&
1366-
sock_fds[i].revents & ZSOCK_POLLIN) {
1426+
if (sock_fds[i].fd == control_sock && sock_fds[i].revents & ZSOCK_POLLIN) {
13671427
zvfs_eventfd_t tmp;
13681428

13691429
zvfs_eventfd_read(sock_fds[i].fd, &tmp);

0 commit comments

Comments
 (0)