LightFTP: Blocking active data connect with stale PORT params leads to DoS
Affected Versions: All tagged releases confirmed: v1.0, v1.1, v2.0, v2.1, v2.2, v2.3, v2.3.1 (latest). Deprecated Windows branch shares the same logic.
Vulnerability Type Denial of Service / Resource Exhaustion
Description: ftpPORT() returns error on address mismatch but leaves previous data_ipv4/data_port intact. create_datasocket() performs blocking connect() without timeout in active mode. If the stale address/port is unreachable, the worker thread blocks until OS-level timeout (≈75s+). While blocked, WorkerThreadValid remains 0; subsequent LIST/RETR/STOR commands for that session receive 550 “Another action is in progress”. Multiple concurrent sessions can exhaust MaxUsers, degrading service availability.
Code analysis:
-
PORT command verification fails and does not reset the status(367:405:LightFTP/src/ftpserv.c) ssize_t ftpPORT(pftp_context context, const char *params) { int c; in_addr_t data_ipv4 = 0, data_port = 0; char *p = (char *)params; // ... if ( data_ipv4 != context->client_ipv4 ) return sendstring(context, error501);// Authentication fails but does not reset data_ipv4/data_port
context->data_ipv4 = data_ipv4;// Only update after verification context->data_port = (in_port_t)data_port; context->mode = MODE_NORMAL;
return sendstring(context, success200); }
Problem: When the PORT command verification fails (such as IP mismatch), the function returns an error, but data_ipv4 and data_port will not be updated or reset. These values may retain the previous state (may be a valid value or an invalid value)
-
connect() call blocks and has no timeout.(113:155:LightFTP/src/ftpserv.c) SOCKET create_datasocket(pftp_context context) { SOCKET client_socket = INVALID_SOCKET; struct sockaddr_in laddr; socklen_t asz;
memset(&laddr, 0, sizeof(laddr));
switch ( context->mode ) { case MODE_NORMAL: client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); context->data_socket = client_socket; if ( client_socket == INVALID_SOCKET ) return INVALID_SOCKET;
laddr.sin_family = AF_INET; laddr.sin_port = context->data_port; laddr.sin_addr.s_addr = context->data_ipv4; // blocking connect, no timeout setting if ( connect(client_socket, (const struct sockaddr *)&laddr, sizeof(laddr)) == -1 ) { close(client_socket); return INVALID_SOCKET; } break;// ... } default: return INVALID_SOCKET; } return client_socket; }
Problem: The connect() call is blocking and the socket timeout option is not set; if data_ipv4 and data_port point to an unreachable address, connect will block until the system times out. The system default connect timeout is usually 75 seconds or longer.
- Worker thread blocking causes function rejection(601:630:LightFTP/src/ftpserv.c) ssize_t ftpLIST(pftp_context context, const char *params) { //... if ((context->worker_thread_valid == 0) || (context->file_fd != -1)) return sendstring(context, error550_t);// "Another action is in progress" //... }
Problem: If the worker thread is blocked in create_datasocket() and WorkerThreadValid remains 0, the new LIST/RETR/STOR command will be rejected (returning 550 error) and the client cannot use the data connection function until the worker thread times out.
POC:
- Login.
- Send valid PORT to an unreachable port on client IP (e.g., 127.0.0.1:65535).
- Send invalid PORT with mismatched IP (gets 501, but stale DataIP/Port kept).
- Send LIST/RETR/STOR → server blocks on connect(), data commands denied for duration.
Impact
- Single session: data-channel commands unusable for the OS timeout window.
- Multi-session: attackers can occupy user slots/threads, leading to service degradation or DoS.
Fix
- Add connect timeouts (non-blocking connect + select/poll, or SO_SNDTIMEO/SO_RCVTIMEO).
- On PORT validation failure, zero
data_ipv4/data_portbefore returning an error. - Optional: cap retries/idle time for active connects.