ftpd: bound the PASV port search instead of looping forever

On a failed bind cmdPASV jumped straight back to .next_port, and
nextpasvport wraps within [start, end], so a range with no usable port
spun forever without reading the control connection again: the client
hangs. The source itself said "TODO: break the endless loop".

Count the ports in the range (end - start + 1), decrement after a
failed bind and give up at zero, then close the listener, reset the
state and answer 425 Can't open data connection. The counter lives in a
new thread_data field rather than a register, because mcall bind
clobbers eax/ebx/ecx/edx/esi/edi and esi already carries the sockaddr
length for bind. Decrementing after the failed bind and not before the
next attempt is what keeps a single-port range (start = end) working:
it still gets its one attempt.

Also validate the pair read from the ini. With start > end the range
degenerates and no port is bindable, so fall back to 2000/5000.

Assisted-by: ZCode:deepseek-flash
This commit is contained in:
Igor Shutrov committed 2026-09-20 23:59:33 +05:00
1 parent 1e05d19c4c
commit 2091d65381
2 files changed
+28 -2

No files matched your search

+20 -2
View File
@@ -14,6 +14,7 @@ struct thread_data
socketnum dd ? ; Commands socket
state dd ? ; disconnected/logging in/logged in/..
passivesocknum dd ? ; when in passive mode, this is the listening socket
pasv_tries dd ? ; ports tried for the current PASV
datasocketnum dd ? ; socket used for data transfers
permissions dd ? ; read/write/execute/....
buffer_ptr dd ?
@@ -847,7 +848,13 @@ cmdPASV:
lea edx, [ebp + thread_data.datasock]
mov esi, sizeof.thread_data.datasock
.next_port: ; TODO: break the endless loop
movzx eax, word [pasv_end]
movzx ebx, word [pasv_start]
sub eax, ebx
inc eax
mov [ebp + thread_data.pasv_tries], eax ; number of ports in the range
.next_port:
call nextpasvport
mov ax, [pasv_port]
xchg al, ah
@@ -855,7 +862,18 @@ cmdPASV:
mcall bind
cmp eax, -1
je .next_port
jne .bound
dec dword [ebp + thread_data.pasv_tries] ; only count a port once bind failed
jnz .next_port
; The range is exhausted, do not spin here forever
mcall close, [ebp + thread_data.passivesocknum]
mov [ebp + thread_data.passivesocknum], -1
call close_data_sock
mov [ebp + thread_data.mode], MODE_NOTREADY
jmp socketerror
.bound:
; And set it to listen!
mcall listen, , 1
+8
View File
@@ -172,6 +172,14 @@ start:
invoke ini.get_int, path, str_pasv, str_end, 5000
mov [pasv_end], ax
; an inverted range would leave no port to bind, fall back to the defaults
mov ax, [pasv_start]
cmp ax, [pasv_end]
jbe @f
mov word [pasv_start], 2000
mov word [pasv_end], 5000
@@:
mov [alive], 1
mainloop: