Skip to content

[Security] Off-by-one heap buffer overflow in fillDirent() "Perm=" strcpy #217

Description

@xhy-520

Vulnerability Summary

mtheall/ftpd v3.2.1 is affected by a one-byte heap buffer overflow
(off-by-one, CWE-193) in the fillDirent() function at
source/ftpSession.cpp:1066-1070.

Affected Component

fillDirent() constructs MLSD/MLST directory listing facts. The
"Perm=" fact handler copies a fixed string into an IOBuffer without
accounting for the null terminator in its length check.

Root Cause

Affected code (source/ftpSession.cpp lines 1066-1070):

auto const header = "Perm="; // 5 characters
if (size - pos < std::strlen(header)) // strlen = 5
return EAGAIN;
std::strcpy(&buffer[pos], header); // writes 6 bytes

strlen("Perm=") returns 5. The check passes when size-pos >= 5.
But strcpy() requires 6 bytes (5 chars + null byte \0).
When size-pos == 5, the check passes (5 < 5 is false),
yet strcpy writes 6 bytes, overflowing by one byte.

Proof of Concept

Compiled with Clang 15 + AddressSanitizer on Ubuntu 24.04:

$ clang++-15 -std=c++20 -fsanitize=address -g -o poc poc.cpp
$ ./poc

ASAN output (key lines):

==ERROR: AddressSanitizer: stack-buffer-overflow
WRITE of size 6 at 0x72355cb00025 thread T0
#1 in bug(char*, unsigned long, unsigned long) poc.cpp:6:5
#2 in main poc.cpp:10:5
This frame has 1 object(s):
[32, 37) 'buf' <== Memory access at offset 37 overflows
Shadow bytes: f1 f1 f1 f1[05]f3 f3 f3
^^ ^^
32-36 buf 37=overflow
SUMMARY: AddressSanitizer: stack-buffer-overflow

A buffer of exactly 5 bytes at position 0 consistently triggers
the overflow. Exit code is 1 (ABORT).

Impact

The buffer in production is a heap-allocated IOBuffer of 65536 bytes
(XFER_BUFFERSIZE on Linux). The overflow writes \0 past the buffer
end into adjacent heap memory. Impact depends on heap layout:

  1. Best case: \0 lands in alignment padding — no observable effect
  2. Likely case: \0 corrupts heap chunk metadata — denial of service
  3. Worst case: \0 corrupts adjacent allocation data — further
    memory corruption

Exploitation requires:

  • Authentication to the FTP server
  • m_mlstPerm flag enabled (default: on)
  • IOBuffer free space exactly 5 bytes when fillDirent() runs

Fix

Line 1067, change:
if (size - pos < std::strlen(header))
To:
if (size - pos < std::strlen(header) + 1)

Affected Versions

All versions from initial release through v3.2.1.
(The fillDirent "Perm=" code has never been modified.)

Discovery

  • Discovered by: Xu Haoyu (徐浩宇)
  • Date: 2026-06-25
  • Method: Source code review + AFL++ fuzzing project + ASAN verification
  • Environment: Clang 15, Ubuntu 24.04 (WSL2), AFL++ 4.32a
  • Vendor notified via GitHub Issue

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions