Project: proftpd (proftpd/proftpd), language: c++
Vulnerability instance: proftpd.cve-2023-51713
Sanitizer: address

================= Bug Report (1/2) ==================
## Source: GitHub Issue
## URL: https://github.com/proftpd/proftpd/issues/1683
## Description:
Issue: proftpd/proftpd#1683
Title: Out-of-bounds buffer read when handling FTP commands
State: closed
Created by: Marti2203
Created at: 2023-06-06 05:37:54+00:00
Labels: bug, backport

Issue Body:
### What I Did

Cloned latest version of ProFTPD (5e57d41) in a container running Ubuntu 20.04, configured with flags for ASAN and UBSan. 
Started the tool as such `./proftpd -n -c <SOMEDIR>/basic.conf -X`.
Sent over a sequence of packets, gathered from a fuzz campaign using the `aflnet-replay` utility. 

### What I Expected/Wanted

Expected to have ProFTPD not crash.

### ProFTPD Version and Configuration
```
Compile-time Settings:
  Version: 1.3.9rc1 (git)
  Platform: LINUX [Linux 5.4.0-120-generic x86_64]
  OS/Release:
    NAME="Ubuntu"
    VERSION="20.04.6 LTS (Focal Fossa)"
    ID=ubuntu
    ID_LIKE=debian
    PRETTY_NAME="Ubuntu 20.04.6 LTS"
    VERSION_ID="20.04"
    VERSION_CODENAME=focal
    UBUNTU_CODENAME=focal
  Built: Tue Jun 6 2023 05:18:02 UTC
  Built With:
    configure  '--enable-devel=nodaemon:nofork' 'CC=cc' 'CFLAGS=-fsanitize=address,undefined -g' 'LDFLAGS=-fsanitize=address,undefined'

  CFLAGS: -g2 -fsanitize=address,undefined -g -Wall -fno-omit-frame-pointer -fno-strict-aliasing -Wdangling-else -Wextra -Werror=implicit-function-declaration -Winit-self -Wno-missing-field-initializers -Wno-unused-parameter -Wnull-derefere
nce -fdelete-null-pointer-checks -DPR_DEVEL_NO_FORK -DPR_DEVEL_NO_DAEMON -g3 -O0 -Wcast-align -Wchar-subscripts -Winline -Wstrict-prototypes -Wmissing-declarations -Wnested-externs -Wpointer-arith -Wshadow -Wundef -Wcomment -Wdangling-else
-Wdeclaration-after-statement -Wduplicated-branches -Wfloat-equal -Wformat -Wformat-security -Wimplicit-function-declaration -Wmaybe-uninitialized -Wmissing-braces -Wparentheses -Wpointer-to-int-cast -Wreturn-local-addr -Wstack-protector -W
strict-overflow -Wswitch -Wunreachable-code -Wunused-variable -fstack-protector -fstack-protector-all -fno-common
  LDFLAGS: -Wl,-L$(top_srcdir)/lib,-L$(top_builddir)/lib -fsanitize=address,undefined -rdynamic
  LIBS:  -lcap -lidn2 -lcrypt

  Files:
    Configuration File:
      /usr/local/etc/proftpd.conf
    Pid File:
      /usr/local/var/proftpd.pid
    Scoreboard File:
      /usr/local/var/proftpd.scoreboard

  Info:
    + Max supported UID: 4294967295
    + Max supported GID: 4294967295

 Features:
    - Autoshadow support
    - Controls support
    + curses support
    + Developer support
    - DSO support
    + IPv6 support
    + Largefile support
    - Lastlog support
    - Memcache support
    + ncurses support
    - NLS support
    - OpenSSL support
    - PCRE support
    - PCRE2 support
    - POSIX ACL support
    - Redis support
    + Sendfile support
    + Shadow file support
    - Sodium support
    + Trace support
    + xattr support

  Tunable Options:
    PR_TUNABLE_BUFFER_SIZE = 1024
    PR_TUNABLE_DEFAULT_RCVBUFSZ = 8192
    PR_TUNABLE_DEFAULT_SNDBUFSZ = 8192
    PR_TUNABLE_ENV_MAX = 2048
    PR_TUNABLE_GLOBBING_MAX_MATCHES = 100000
    PR_TUNABLE_GLOBBING_MAX_RECURSION = 8
    PR_TUNABLE_HASH_TABLE_SIZE = 40
    PR_TUNABLE_LOGIN_MAX = 256
    PR_TUNABLE_NEW_POOL_SIZE = 512
    PR_TUNABLE_PATH_MAX = 4096
    PR_TUNABLE_SCOREBOARD_BUFFER_SIZE = 80
    PR_TUNABLE_SCOREBOARD_SCRUB_TIMER = 30
    PR_TUNABLE_SELECT_TIMEOUT = 30
    PR_TUNABLE_TIMEOUTIDENT = 10
    PR_TUNABLE_TIMEOUTIDLE = 600
    PR_TUNABLE_TIMEOUTLINGER = 10
    PR_TUNABLE_TIMEOUTLOGIN = 300
    PR_TUNABLE_TIMEOUTNOXFER = 300
    PR_TUNABLE_TIMEOUTSTALLED = 3600
    PR_TUNABLE_XFER_SCOREBOARD_UPDATES = 10
```
In addition, we need to see **all** of the ProFTPD configuration files you are
using (**minus** any sensitive information like passwords, of course).  Armed
with the version and configuration data, then, we can set up ProFTPD locally
using the same configuration, and see what happens.
Config and input are provided in the archive below
[data.tar.gz](https://github.com/proftpd/proftpd/files/11660286/data.tar.gz)



Comments:
Comment by Marti2203 on 2023-06-06 05:42:30+00:00:
Output of ASAN
```
==2288394==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x62100001a320 at pc 0x562e102ac29d bp 0x7ffc9a7ba260 sp 0x7ffc9a7ba250
READ of size 1 at 0x62100001a320 thread T0
    #0 0x562e102ac29c in make_ftp_cmd /home/ubuntu/experiments/proftpd-clean/src/main.c:862
    #1 0x562e102a9a11 in pr_cmd_read /home/ubuntu/experiments/proftpd-clean/src/main.c:571
    #2 0x562e102acc88 in cmd_loop /home/ubuntu/experiments/proftpd-clean/src/main.c:910
    #3 0x562e102b028a in fork_server /home/ubuntu/experiments/proftpd-clean/src/main.c:1519
    #4 0x562e102b1834 in daemon_loop /home/ubuntu/experiments/proftpd-clean/src/main.c:1764
    #5 0x562e102b2106 in standalone_main /home/ubuntu/experiments/proftpd-clean/src/main.c:1965
    #6 0x562e102b4928 in main /home/ubuntu/experiments/proftpd-clean/src/main.c:2790
    #7 0x7fe618414082 in __libc_start_main ../csu/libc-start.c:308
    #8 0x562e102a537d in _start (/home/ubuntu/experiments/proftpd-clean/proftpd+0x46437d)

0x62100001a320 is located 0 bytes to the right of 4640-byte region [0x621000019100,0x62100001a320)
allocated by thread T0 here:
    #0 0x7fe6190c1808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
    #1 0x562e102ba510 in smalloc /home/ubuntu/experiments/proftpd-clean/src/pool.c:114
    #2 0x562e102ba56a in malloc_block /home/ubuntu/experiments/proftpd-clean/src/pool.c:127
    #3 0x562e102baf70 in new_block /home/ubuntu/experiments/proftpd-clean/src/pool.c:219
    #4 0x562e102bf451 in alloc_pool /home/ubuntu/experiments/proftpd-clean/src/pool.c:672
    #5 0x562e102bf734 in palloc /home/ubuntu/experiments/proftpd-clean/src/pool.c:684
    #6 0x562e102bf846 in pcalloc /home/ubuntu/experiments/proftpd-clean/src/pool.c:699
    #7 0x562e102a9144 in pr_cmd_read /home/ubuntu/experiments/proftpd-clean/src/main.c:494
    #8 0x562e102acc88 in cmd_loop /home/ubuntu/experiments/proftpd-clean/src/main.c:910
    #9 0x562e102b028a in fork_server /home/ubuntu/experiments/proftpd-clean/src/main.c:1519
    #10 0x562e102b1834 in daemon_loop /home/ubuntu/experiments/proftpd-clean/src/main.c:1764
    #11 0x562e102b2106 in standalone_main /home/ubuntu/experiments/proftpd-clean/src/main.c:1965
    #12 0x562e102b4928 in main /home/ubuntu/experiments/proftpd-clean/src/main.c:2790
    #13 0x7fe618414082 in __libc_start_main ../csu/libc-start.c:308

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/ubuntu/experiments/proftpd-clean/src/main.c:862 in make_ftp_cmd
Shadow bytes around the buggy address:
  0x0c427fffb410: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c427fffb420: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c427fffb430: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c427fffb440: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c427fffb450: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c427fffb460: 00 00 00 00[fa]fa fa fa fa fa fa fa fa fa fa fa
  0x0c427fffb470: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c427fffb480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c427fffb490: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c427fffb4a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c427fffb4b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==2288394==ABORTING
Aborted

---

Comment by Castaglia on 2023-06-10 16:13:02+00:00:
@Marti2203 Thanks for the report.  I'm attempting to reproduce this behavior locally now.  How are you feeding the provided `input` file to the daemon?  I've tried using _e.g._ `telnet 127.0.0.1 21 < /path/to/input`, but haven't yet reproduced the ASAN crash, and I think I might be missing some tool or step.

I just installed [aflnet](https://github.com/aflnet/aflnet), and attempting to use its `aflnet-replay` now:
```
$ aflnet-replay /path/to/input FTP 21
```

---

Comment by Castaglia on 2023-06-10 21:56:04+00:00:
Out of curiosity, which ASAN options to you have set in your environment?  For example, in my current local setup to try to reproduce this, I am using:
```
ASAN_OPTIONS=verbosity=3,detect_leaks=0,abort_on_error=1,debug=true,check_initialization_order=true,detect_stack_use_after_return=true,strict_string_checks=true,detect_invalid_pointer_pairs=2
```

---

Comment by Marti2203 on 2023-06-11 02:07:44+00:00:
Hi @Castaglia, I used these options to reproduce it
`ASAN_OPTIONS="abort_on_error=1:symbolize=1:detect_leaks=1:detect_stack_use_after_return=1:detect_container_overflow=1:poison_array_cookie=1:malloc_fill_byte=0:max_malloc_fill_size=16777216"`

Were you able to reproduce it earlier?

---

Comment by Castaglia on 2023-06-15 03:01:21+00:00:
Unfortunately no, I've not been able to reproduce the ASAN report yet.  I suspect I know the cause -- but it would greatly help to have a working reproduction recipe, to verify a fix.  Would you be able to provide a Dockerfile (and instructions), that I might run the same locally?  Thanks!

---

Comment by Marti2203 on 2023-06-15 03:02:37+00:00:
I can send a docker container image with the instance running and all. Will need 1-2 hours and get back to you :)

---

Comment by Marti2203 on 2023-06-15 03:09:24+00:00:
[Link to the docker image](https://drive.google.com/file/d/1RuS1TQdqfA5JtnjCSXs7fzYH4vVc2JFa/view?usp=sharing)

Steps for reproduction:
* Docker load the tar.gz (`docker load < file.tar.gz` )
* Create an instance of the container
* In one terminal in the directory `/home/ubuntu/experiments/proftpd` , run `./proftpd -n -c /home/ubuntu/replayable-crashes-proftpd/basic.conf -X`
* In another terminal in the directory `/home/ubuntu/replayable-crashes-proftpd` run `aflnet-replay input FTP 21 1`
* ProFTPD should crash in the first terminal



---

Comment by Castaglia on 2023-06-15 03:16:41+00:00:
Thanks!  I might not have free time to spend on this until this weekend, but I will take a look at the provided Dockerfile; many thanks!

---

Comment by Marti2203 on 2023-06-15 03:17:19+00:00:
No problem :) 

---

Comment by Castaglia on 2023-06-15 03:20:19+00:00:
Happen to have the actual `Dockerfile` as well, so that I can build the image locally?

---

Comment by Marti2203 on 2023-06-15 03:21:51+00:00:
Yes, just a moment

---

Comment by Castaglia on 2023-06-15 03:27:47+00:00:
If it helps, here's what I've been building up locally:
```
FROM ubuntu:20.04

ARG USER=docker
ARG PASSWORD=docker

ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=UTC

RUN apt-get update && \
  apt-get install -y \
    clang \
    ftp \
    git \
    graphviz-dev \
    libcap-dev \
    make \
    netcat \
    telnet \
    vim

# Create the account, and files, used for testing
RUN mkdir -p /var/log/proftpd
RUN useradd -s /bin/bash -m -d "/home/$USER" "$USER" && \
  yes "$PASSWORD" | passwd "$USER"

# Install AFLnet
RUN git clone --depth 10 https://github.com/aflnet/aflnet.git /src/aflnet
RUN cd /src/aflnet && \
  make clean all && \
  cd llvm_mode/ && \
  LLVM_CONFIG=/usr/bin/llvm-config-10 make

ENV AFLNET=/src/aflnet
ENV AFL_PATH=/src/aflnet

COPY commands.txt /src/commands.txt

ENV ASAN_OPTIONS=abort_on_error=1:symbolize=1:detect_leaks=1:detect_stack_use_after_return=1:detect_container_overflow=1:poison_array_cookie=1:malloc_fill_byte=0:max_malloc_fill_size=16777216

# Run these locally with an outdated Mac OSX Docker Desktop installation
# $ docker run --security-opt seccomp=unconfined --privileged --rm=true -it -v $(PWD):/src/proftpd heap-overflow-issue1683 bash
# cd /src/proftpd

# CC=clang CFLAGS=-fsanitize=address,undefined LDFLAGS=-fsanitize=address,undefined ./configure --enable-devel=nodaemon:nofork
# make
# make install
# /usr/local/sbin/proftpd -X -nd10

CMD ["bash"]
```

---

Comment by Marti2203 on 2023-06-15 05:32:13+00:00:

[proftpd-repro.tar.gz](https://github.com/proftpd/proftpd/files/11753209/proftpd-repro.tar.gz)

Command for executing proftpd - `./proftpd -n -c ${WORKDIR}/basic.conf -X`
Command for executing the aflnet-replay `aflnet-replay input FTP 21 1`


---

Comment by Castaglia on 2023-08-06 16:46:38+00:00:
Many apologies for the delay; I've been dealing with other things.

Using the provided tarball (with some minor tweaks for my local Mac-based environment), I'm now able to reproduce the report from `aflnet`.  Thanks!  Now to see about fixing this issue properly.

---

Comment by Castaglia on 2023-08-06 20:26:34+00:00:
@Marti2203 https://github.com/proftpd/proftpd/pull/1696 looks to address this issue using your reproduction recipe, without causing any other regressions that I've seen so far.  Care to try it out yourself, see if there any other related issues lingering?  Thanks!

---

Comment by Castaglia on 2023-09-10 17:25:08+00:00:
I've merged the PR to master, and backported the fix to the 1.3.8 branch.  Thanks!

Attachments:
https://github.com/proftpd/proftpd/files/11660286/data.tar.gz
https://github.com/proftpd/proftpd/files/11753209/proftpd-repro.tar.gz

================= Bug Report (2/2) ==================
## Source: GitHub Issue
## URL: https://github.com/proftpd/proftpd/issues/1683#issuecomment-1712887554
## Description:
Issue: proftpd/proftpd#1683
Title: Out-of-bounds buffer read when handling FTP commands
Referenced comment by Castaglia on 2023-09-10 17:25:08+00:00:

I've merged the PR to master, and backported the fix to the 1.3.8 branch.  Thanks!

Build script:
#!/bin/bash
# Default build script template for C/C++ projects
set -eu

# Define configuration options
CONFIG_OPTS="--disable-shared"
CMAKE_OPTS="-DCMAKE_BUILD_TYPE=Debug"
MAKE_OPTS="-j$(nproc)"
BUILD_DIR="build"

# 1. Run build preparation scripts if they exist
for prep_script in buildconf autogen.sh bootstrap; do
  if [ -x "$prep_script" ]; then
    echo "Running $prep_script..."
    ./$prep_script
    break
  elif [ -f "$prep_script" ]; then
    echo "Found $prep_script but it's not executable, setting permissions..."
    chmod +x $prep_script
    ./$prep_script
    break
  fi
done

# 2. Handle different build systems
if [ -f "configure" ]; then
  echo "Found configure script, running with options: $CONFIG_OPTS"
  ./configure $CONFIG_OPTS

  echo "Building with make $MAKE_OPTS"
  make $MAKE_OPTS
elif [ -f "CMakeLists.txt" ]; then
  echo "Found CMakeLists.txt, using CMake build system"
  mkdir -p $BUILD_DIR
  cd $BUILD_DIR
  cmake $CMAKE_OPTS ..
  make $MAKE_OPTS
elif [ -f "Makefile" ] || [ -f "makefile" ]; then
  echo "Found Makefile, building with make $MAKE_OPTS"
  make $MAKE_OPTS
elif [ -f "meson.build" ]; then
  echo "Found meson.build, using Meson build system"
  mkdir -p $BUILD_DIR
  meson setup $BUILD_DIR .
  cd $BUILD_DIR
  ninja
elif [ -f "GNUmakefile" ]; then
  echo "Found GNUmakefile, building with make $MAKE_OPTS"
  make $MAKE_OPTS
elif [ -d ".git" ] && [ -f "Makefile.am" ]; then
  echo "Found Makefile.am, running autoreconf"
  autoreconf -i
  ./configure $CONFIG_OPTS
  make $MAKE_OPTS
else
  echo "WARNING: No recognized build system found!"
  echo "Trying generic approach:"
  
  # Try to detect common source patterns and compile them
  if ls *.c >/dev/null 2>&1 || ls *.cpp >/dev/null 2>&1 || ls *.cc >/dev/null 2>&1; then
    echo "Found C/C++ source files, attempting direct compilation"
    if ls *.cpp >/dev/null 2>&1 || ls *.cc >/dev/null 2>&1; then
      CXX=${CXX:-g++}
      echo "Compiling C++ sources with $CXX"
      $CXX -g -O1 -fsanitize=address -fno-omit-frame-pointer -o main *.cpp *.cc 2>/dev/null || true
    elif ls *.c >/dev/null 2>&1; then
      CC=${CC:-gcc}
      echo "Compiling C sources with $CC"
      $CC -g -O1 -fsanitize=address -fno-omit-frame-pointer -o main *.c 2>/dev/null || true
    fi
  fi
fi

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt-get install -y make autoconf automake libtool pkg-config cmake check
RUN git clone https://github.com/proftpd/proftpd proftpd
RUN git -C proftpd checkout 0ca30300519ae922c43ae1b360db83b97f8e4115
WORKDIR $SRC/proftpd
COPY build.sh $SRC/

Additional files: