Vulnerability: In Pure-FTPd 1.0.49, a stack exhaustion issue was discovered in the listdir function in ls.c.
Vulnerability ID: CVE-2019-20176
CWE: CWE-400
Severity score: 2.9
Project: https://github.com/jedisct1/pure-ftpd
Published: 2019-12-31
File: src/ls.c
Commit message: listdir(): reuse a single buffer to store every file name to display

Allocating a new buffer for each entry is useless.

And as these buffers are allocated on the stack, on systems with a
small stack size, with many entries, the limit can easily be reached,
causing a stack exhaustion and aborting the user session.

Reported by Antonio Morales from the GitHub Security Lab team, thanks!
Commit author: Frank Denis

Vulnerable version (before fix):
 static void listdir(unsigned int depth, int f, void * const tls_fd,     char *names;     PureFileInfo *s;     PureFileInfo *r;     int d;      if (depth >= max_ls_depth || matches >= max_ls_files) { static void listdir(unsigned int depth, int f, void * const tls_fd,     }     outputfiles(f, tls_fd);     r = dir;     while (opt_R && r != s) {         if (r->name_offset != (size_t) -1 && !chdir(FI_NAME(r))) {             char *alloca_subdir;             const size_t sizeof_subdir = PATH_MAX + 1U;              if ((alloca_subdir = ALLOCA(sizeof_subdir)) == NULL) {                 goto toomany;             }             if (SNCHECK(snprintf(alloca_subdir, sizeof_subdir, "%s/%s",                                  name, FI_NAME(r)), sizeof_subdir)) {                 goto nolist; static void listdir(unsigned int depth, int f, void * const tls_fd,             wrstr(f, tls_fd, alloca_subdir);             wrstr(f, tls_fd, ":\r\n\r\n");             listdir(depth + 1U, f, tls_fd, alloca_subdir);             nolist:             ALLOCA_FREE(alloca_subdir);             if (matches >= max_ls_files) {                 goto toomany;             } static void listdir(unsigned int depth, int f, void * const tls_fd,         r++;     }     toomany:     free(names);     free(dir);     names = NULL;

Fixed version (after fix):
 static void listdir(unsigned int depth, int f, void * const tls_fd,     char *names;     PureFileInfo *s;     PureFileInfo *r;     char *alloca_subdir;     size_t sizeof_subdir;     int d;      if (depth >= max_ls_depth || matches >= max_ls_files) { static void listdir(unsigned int depth, int f, void * const tls_fd,     }     outputfiles(f, tls_fd);     r = dir;     sizeof_subdir = PATH_MAX + 1U;     if ((alloca_subdir = ALLOCA(sizeof_subdir)) == NULL) {         goto toomany;     }     while (opt_R && r != s) {         if (r->name_offset != (size_t) -1 && !chdir(FI_NAME(r))) {             if (SNCHECK(snprintf(alloca_subdir, sizeof_subdir, "%s/%s",                                  name, FI_NAME(r)), sizeof_subdir)) {                 goto nolist; static void listdir(unsigned int depth, int f, void * const tls_fd,             wrstr(f, tls_fd, alloca_subdir);             wrstr(f, tls_fd, ":\r\n\r\n");             listdir(depth + 1U, f, tls_fd, alloca_subdir);              nolist:             if (matches >= max_ls_files) {                 goto toomany;             } static void listdir(unsigned int depth, int f, void * const tls_fd,         r++;     }     toomany:     ALLOCA_FREE(alloca_subdir);     free(names);     free(dir);     names = NULL;

Patch:
patch: @@ -661,6 +661,8 @@ static void listdir(unsigned int depth, int f, void * const tls_fd,
     char *names;
     PureFileInfo *s;
     PureFileInfo *r;
+    char *alloca_subdir;
+    size_t sizeof_subdir;
     int d;
 
     if (depth >= max_ls_depth || matches >= max_ls_files) {
@@ -690,14 +692,12 @@ static void listdir(unsigned int depth, int f, void * const tls_fd,
     }
     outputfiles(f, tls_fd);
     r = dir;
+    sizeof_subdir = PATH_MAX + 1U;
+    if ((alloca_subdir = ALLOCA(sizeof_subdir)) == NULL) {
+        goto toomany;
+    }
     while (opt_R && r != s) {
         if (r->name_offset != (size_t) -1 && !chdir(FI_NAME(r))) {
-            char *alloca_subdir;
-            const size_t sizeof_subdir = PATH_MAX + 1U;
-
-            if ((alloca_subdir = ALLOCA(sizeof_subdir)) == NULL) {
-                goto toomany;
-            }
             if (SNCHECK(snprintf(alloca_subdir, sizeof_subdir, "%s/%s",
                                  name, FI_NAME(r)), sizeof_subdir)) {
                 goto nolist;
@@ -706,8 +706,8 @@ static void listdir(unsigned int depth, int f, void * const tls_fd,
             wrstr(f, tls_fd, alloca_subdir);
             wrstr(f, tls_fd, ":\r\n\r\n");
             listdir(depth + 1U, f, tls_fd, alloca_subdir);
+
             nolist:
-            ALLOCA_FREE(alloca_subdir);
             if (matches >= max_ls_files) {
                 goto toomany;
             }
@@ -720,6 +720,7 @@ static void listdir(unsigned int depth, int f, void * const tls_fd,
         r++;
     }
     toomany:
+    ALLOCA_FREE(alloca_subdir);
     free(names);
     free(dir);
     names = NULL;
path: src/ls.c