lwip_netconn_do_writemore(conn WRITE_DELAYED);
}
} else if (conn->state == NETCONN_CLOSE) {
+ /* Before calling lwip_netconn_do_close_internal, check if pcb and current_msg are valid.
+ * If either is NULL, it means the connection was closed (err_tcp was called).
+ * err_tcp should have already set state to NETCONN_NONE, but if there's a race condition,
+ * we just skip processing (defensive check, not state change). */
+ if (conn->pcb.tcp == NULL || conn->current_msg == NULL) {
+ /* Connection was closed by err_tcp. Nothing to do (defensive check only). */
+ } else {
#if !LWIP_SO_SNDTIMEO && !LWIP_SO_LINGER
- if (conn->current_msg && conn->current_msg->msg.sd.polls_left) {
- conn->current_msg->msg.sd.polls_left--;
- }
+ if (conn->current_msg->msg.sd.polls_left) {
+ conn->current_msg->msg.sd.polls_left--;
+ }
#endif /* !LWIP_SO_SNDTIMEO && !LWIP_SO_LINGER */
- lwip_netconn_do_close_internal(conn WRITE_DELAYED);
+ lwip_netconn_do_close_internal(conn WRITE_DELAYED);
+ }
}
/* @todo: implement connect timeout here? */
lwip_netconn_do_writemore(conn WRITE_DELAYED);
}
} else if (conn->state == NETCONN_CLOSE) {
- lwip_netconn_do_close_internal(conn WRITE_DELAYED);
+ /* Before calling lwip_netconn_do_close_internal, check if pcb and current_msg are valid.
+ * If either is NULL, it means the connection was closed (err_tcp was called).
+ * err_tcp should have already set state to NETCONN_NONE, but if there's a race condition,
+ * we just skip processing (defensive check, not state change). */
+ if (conn->pcb.tcp == NULL || conn->current_msg == NULL) {
+ /* Connection was closed by err_tcp. Nothing to do (defensive check only). */
+ } else {
+ lwip_netconn_do_close_internal(conn WRITE_DELAYED);
+ }
}
/* If the queued byte- or pbuf-count drops below the configured low-water limit,
struct netconn *conn;
enum netconn_state old_state;
void *mbox_msg;
+ sys_sem_t *op_completed_sem = NULL;
+ int need_signal = 0;
SYS_ARCH_DECL_PROTECT(lev);
conn = (struct netconn *)arg;
old_state = conn->state;
conn->state = NETCONN_NONE;
+ /* Fix: Protect current_msg cleanup within the lock to prevent race condition
+ * with lwip_netconn_do_writemore() which may be accessing current_msg concurrently */
+ if ((old_state == NETCONN_WRITE) || (old_state == NETCONN_CLOSE) ||
+ (old_state == NETCONN_CONNECT)) {
+ int was_nonblocking_connect = IN_NONBLOCKING_CONNECT(conn);
+ SET_NONBLOCKING_CONNECT(conn, 0);
+
+ if (!was_nonblocking_connect) {
+ /* set error return code - do this within lock protection */
+ if (conn->current_msg != NULL) {
+ if (old_state == NETCONN_CLOSE) {
+ /* let close succeed: the connection is closed after all... */
+ conn->current_msg->err = ERR_OK;
+ } else {
+ /* Write and connect fail */
+ conn->current_msg->err = err;
+ }
+ op_completed_sem = LWIP_API_MSG_SEM(conn->current_msg);
+ LWIP_ASSERT("inavlid op_completed_sem", sys_sem_valid(op_completed_sem));
+ /* Clear current_msg within lock protection to prevent race condition */
+ conn->current_msg = NULL;
+ need_signal = 1;
+ }
+ }
+ } else {
+ LWIP_ASSERT("conn->current_msg == NULL", conn->current_msg == NULL);
+ }
+
SYS_ARCH_UNPROTECT(lev);
/* Notify the user layer about a connection error. Used to signal select. */
sys_mbox_trypost(&conn->acceptmbox, mbox_msg);
}
- if ((old_state == NETCONN_WRITE) || (old_state == NETCONN_CLOSE) ||
- (old_state == NETCONN_CONNECT)) {
- /* calling lwip_netconn_do_writemore/lwip_netconn_do_close_internal is not necessary
- since the pcb has already been deleted! */
- int was_nonblocking_connect = IN_NONBLOCKING_CONNECT(conn);
- SET_NONBLOCKING_CONNECT(conn, 0);
-
- if (!was_nonblocking_connect) {
- sys_sem_t *op_completed_sem;
- /* set error return code */
- LWIP_ASSERT("conn->current_msg != NULL", conn->current_msg != NULL);
- if (old_state == NETCONN_CLOSE) {
- /* let close succeed: the connection is closed after all... */
- conn->current_msg->err = ERR_OK;
- } else {
- /* Write and connect fail */
- conn->current_msg->err = err;
- }
- op_completed_sem = LWIP_API_MSG_SEM(conn->current_msg);
- LWIP_ASSERT("inavlid op_completed_sem", sys_sem_valid(op_completed_sem));
- conn->current_msg = NULL;
- /* wake up the waiting task */
- sys_sem_signal(op_completed_sem);
- } else {
- /* @todo: test what happens for error on nonblocking connect */
- }
- } else {
- LWIP_ASSERT("conn->current_msg == NULL", conn->current_msg == NULL);
+ /* Wake up the waiting task outside of lock to avoid potential deadlock */
+ if (need_signal && op_completed_sem != NULL) {
+ sys_sem_signal(op_completed_sem);
}
}
LWIP_ASSERT("conn->state == NETCONN_WRITE", (conn->state == NETCONN_WRITE));
LWIP_ASSERT("conn->current_msg != NULL", conn->current_msg != NULL);
LWIP_ASSERT("conn->pcb.tcp != NULL", conn->pcb.tcp != NULL);
+
+ /* Defensive check: verify pcb and current_msg are still valid before accessing them.
+ * These can become invalid if err_tcp was called between poll_tcp's check and this function. */
+ if (conn->pcb.tcp == NULL || conn->current_msg == NULL || conn->state != NETCONN_WRITE) {
+ /* Connection was closed by err_tcp or state changed. Return error immediately. */
+ return ERR_CONN;
+ }
+
LWIP_ASSERT("conn->current_msg->msg.w.offset < conn->current_msg->msg.w.len",
conn->current_msg->msg.w.offset < conn->current_msg->msg.w.len);
LWIP_ASSERT("conn->current_msg->msg.w.vector_cnt > 0", conn->current_msg->msg.w.vector_cnt > 0);
dontblock = netconn_is_nonblocking(conn) || (apiflags & NETCONN_DONTBLOCK);
#if LWIP_SO_SNDTIMEO
- if ((conn->send_timeout != 0) &&
+ /* Re-check current_msg validity before accessing timeout fields */
+ if (conn->current_msg != NULL && (conn->send_timeout != 0) &&
((s32_t)(sys_now() - conn->current_msg->msg.w.time_started) >= conn->send_timeout)) {
write_finished = 1;
if (conn->current_msg->msg.w.offset == 0) {
if (dontblock) {
if (!len) {
/* set error according to partial write or not */
+ /* Re-check current_msg validity before accessing it */
+ if (conn->current_msg == NULL) {
+ err = ERR_CONN;
+ write_finished = 1;
+ break;
+ }
err = (conn->current_msg->msg.w.offset == 0) ? ERR_WOULDBLOCK : ERR_OK;
goto err_mem;
}
apiflags |= TCP_WRITE_FLAG_MORE;
}
}
+ /* Re-check current_msg and vector validity before accessing them */
+ if (conn->current_msg == NULL || conn->current_msg->msg.w.vector == NULL) {
+ err = ERR_CONN;
+ write_finished = 1;
+ break;
+ }
LWIP_ASSERT("lwip_netconn_do_writemore: invalid length!",
((conn->current_msg->msg.w.vector_off + len) <= conn->current_msg->msg.w.vector->len));
/* we should loop around for more sending in the following cases:
}
err = tcp_write(conn->pcb.tcp, dataptr, len, apiflags);
if (err == ERR_OK) {
+ /* Re-check current_msg validity after tcp_write (err_tcp may have been called) */
+ if (conn->current_msg == NULL) {
+ err = ERR_CONN;
+ write_finished = 1;
+ break;
+ }
conn->current_msg->msg.w.offset += len;
conn->current_msg->msg.w.vector_off += len;
/* check if current vector is finished */
+ /* Re-check current_msg and vector validity before accessing vector->len */
+ if (conn->current_msg == NULL || conn->current_msg->msg.w.vector == NULL) {
+ err = ERR_CONN;
+ write_finished = 1;
+ break;
+ }
if (conn->current_msg->msg.w.vector_off == conn->current_msg->msg.w.vector->len) {
conn->current_msg->msg.w.vector_cnt--;
/* if we have additional vectors, move on to them */
if (err == ERR_OK) {
err_t out_err;
- if ((conn->current_msg->msg.w.offset == conn->current_msg->msg.w.len) || dontblock) {
- /* return sent length (caller reads length from msg.w.offset) */
- write_finished = 1;
- }
- /* Re-check pcb validity before calling tcp_output */
- if (conn->pcb.tcp == NULL) {
+ /* Re-check current_msg and pcb validity before accessing them */
+ if (conn->current_msg == NULL || conn->pcb.tcp == NULL) {
+ /* Connection was closed or message invalidated during write */
err = ERR_CONN;
write_finished = 1;
} else {
- out_err = tcp_output(conn->pcb.tcp);
- if (out_err == ERR_RTE) {
- /* If tcp_output fails because no route is found,
- don't try writing any more but return the error
- to the application thread. */
- err = out_err;
+ if ((conn->current_msg->msg.w.offset == conn->current_msg->msg.w.len) || dontblock) {
+ /* return sent length (caller reads length from msg.w.offset) */
write_finished = 1;
}
+ /* Re-check pcb validity before calling tcp_output */
+ if (conn->pcb.tcp == NULL) {
+ err = ERR_CONN;
+ write_finished = 1;
+ } else {
+ out_err = tcp_output(conn->pcb.tcp);
+ if (out_err == ERR_RTE) {
+ /* If tcp_output fails because no route is found,
+ don't try writing any more but return the error
+ to the application thread. */
+ err = out_err;
+ write_finished = 1;
+ }
+ }
}
} else if (err == ERR_MEM) {
/* If ERR_MEM, we wait for sent_tcp or poll_tcp to be called.
} else if (dontblock) {
/* non-blocking write is done on ERR_MEM, set error according
to partial write or not */
- err = (conn->current_msg->msg.w.offset == 0) ? ERR_WOULDBLOCK : ERR_OK;
- write_finished = 1;
+ /* Re-check current_msg validity before accessing it */
+ if (conn->current_msg == NULL) {
+ err = ERR_CONN;
+ write_finished = 1;
+ } else {
+ err = (conn->current_msg->msg.w.offset == 0) ? ERR_WOULDBLOCK : ERR_OK;
+ write_finished = 1;
+ }
}
}
} else {
if (write_finished) {
/* everything was written: set back connection state
and back to application task */
- sys_sem_t *op_completed_sem = LWIP_API_MSG_SEM(conn->current_msg);
- conn->current_msg->err = err;
- /* Reset state BEFORE clearing current_msg to avoid race condition:
- * If poll_tcp/sent_tcp is called between these two assignments,
- * we want state to be NETCONN_NONE (not NETCONN_WRITE) when current_msg is NULL */
- conn->state = NETCONN_NONE;
- conn->current_msg = NULL;
+ /* Re-check current_msg validity before accessing it (err_tcp may have cleared it) */
+ if (conn->current_msg != NULL) {
+ /* Normal cleanup: current_msg is still valid, so we need to clean up */
+ sys_sem_t *op_completed_sem = LWIP_API_MSG_SEM(conn->current_msg);
+ conn->current_msg->err = err;
+ /* Reset state BEFORE clearing current_msg to avoid race condition:
+ * If poll_tcp/sent_tcp is called between these two assignments,
+ * we want state to be NETCONN_NONE (not NETCONN_WRITE) when current_msg is NULL */
+ conn->state = NETCONN_NONE;
+ conn->current_msg = NULL;
#if LWIP_TCPIP_CORE_LOCKING
- if (delayed)
+ if (delayed)
#endif
- {
- sys_sem_signal(op_completed_sem);
+ {
+ sys_sem_signal(op_completed_sem);
+ }
+ } else {
+ /* current_msg was already cleared by err_tcp.
+ * err_tcp should have already set state to NETCONN_NONE.
+ * This is a defensive check: we don't modify state here,
+ * we just skip cleanup (defensive check, not state change). */
}
}
#if LWIP_TCPIP_CORE_LOCKING