I'll Crash your Netflix TV: finding CVE-2019-10028

TL;DR

Finding an out-of-bounds read on a Netflix-DIAL server with Mayhem

Introduction

This month, while testing Mayhem on various open-source projects, one open-source project had some fruitful Mayhem results. The Netflix Dial Server located in the Netflix Github repository had a remote out-of-bounds read which Mayhem discovered.

Since many other fuzz testers are unable to fuzz network communication efficiently, most webserver-like programs are full of bugs that Mayhem can trigger.

Background

In early 2013 Netflix was working on a project called Discovery And Launch, better known as DIAL. The DIAL server was one of the first prototypes for streaming Netflix and YouTube directly from your smartphone to your television. The DIAL server would come preinstalled on most TVs of the time. If this all sounds too familiar, it’s because Google created a device called Google Chromecast that does the same thing. Google Chromecast originally used DIAL as its means of communication but eventually switched to mDNS–thus began the end of DIAL’s usage. Although DIAL stopped shipping with new televisions, it’s not clear that DIAL is not purged from modern Netflix and YouTube applications. After a small investigation, we found that the DIAL server could still communicate with an iPhone–making this an interesting target.

iPhone POC

Recon

Looking at the dial-reference readme, it says: “The DIAL client uses CURL to send HTTP request commands to the DIAL server,” which is right up Mayhem’s alley of operation. Quickly reversing some of the dial-server code shows that the server listens over a constant IP Address and port.

dial_server.c

42 // TODO: Partners should define this port
43 #define DIAL_PORT (56789)
44 #define DIAL_DATA_SIZE (8*1024)
45
46 static const char *gLocalhost = "127.0.0.1";
47

The main component of the DIAL server is the Mongoose server, which is based on the original Mongoose embedded server released around the same time as DIAL. This code is significantly modified to be as compact as possible.

mongoose.h

22 // NOTE: This is a SEVERELY stripped down version of mongoose [...] 

Weighing in at about 960 lines, the mongoose.c file had the potential to be buggy, so we decided to finally start Mayhem on the DIAL Server.

Harnessing

Harnessing is usually one of the harder tasks when fuzzing webservers, but luckily the engineers at ForAllSecure have had their fair share of fuzzing. Simply running the Mayhem packaging system on the DIAL Server binary collected all the linked libraries and placed them into a well formatted file. The last part of harnessing was simply specifying what networking protocol (TCP or UDP), what port, and what IP to fuzz on–presto, we have a fuzzer.

Fuzzing Performance

Mayhem did surprisingly well on the binary without modification to the source code or customized compilation. Executions clocked in at around 1400 executions per second, which is not amazing but still awesome. The server explored branches relatively fast and found a crash within the first 24 hours.

Triaging Rouge Requests

The DIAL server crashed with a SIGSEGV showing a backtrace culprit of discard_current_request_from_buffer function. After spending some time tracking the passing of bytes and requests, it was clear that the problem was coming from the mongoose.c code.

f 0     7ffff774fc3c __memmove_avx_unaligned_erms+364
f 1     55555555b605 discard_current_request_from_buffer+252
f 2     55555555b89f process_new_connection+663
f 3     55555555bb79 worker_thread+284
f 4     7ffff7bbd6db start_thread+219

When the dial-server program is launched it spawns multiple threads and makes a call to mongoose.c to process the incoming connections.

The function process_new_connection is called to process the current request. The requested content-length, specified in the GET request, is placed into the variable conn->request_len and later used in a memmove call for an address offset.

The vulnerability is found in the check on the request length. On line 734 an assert is called: assert(conn->data_len >= conn->request_len). Though the assert checks for conn->request_len to be less than the allocated data_len, the assert does not check if the conn->request_len is negative. No check on the value allows this value to be passed to the discard_current_request_from_buffer function as the new content_len.

734   assert(conn->data_len >= conn->request_len);
735   if (conn->request_len == 0 && conn->data_len == conn->buf_size) {
736     mg_send_http_error(conn, 413, "Request Too Large", "");
737     return;
738   } if (conn->request_len <= 0) {
739     return;  // Remote end closed the connection
740   }

Once content_len makes it to the discard function, another check is made on line 712 to check the content_len is smaller than the buffered size: else if (conn->content_len < (int64_t) buffered_len) which will pass and do: body_len = (int) conn->content_len; This allows a large 64-bit negative value (larger than 32 bit), to be converted into 32 bits, which allows the negativeness to be dropped. So a value passed of length: -13377777777777 (0xfffff3d53e4ec38f), gets converted to 1045349263–allowing an arbitrary length read of a memory location.

712     else if (conn->content_len < (int64_t) buffered_len) 
713     body_len = (int) conn->content_len;

If the passed size is of a certain size, it will cause a memmove of a non-readable memory location, which will crash the program.

Crashing Example

When sent to the server, it causes a SIGSEGV crash.

GET /foo HTTP/1.1
Content-Length:-12377777775679

Simple Fix, Complex Benefit

Since the bug is found in the length check, a fix could be as simple as changing: assert(conn->data_len >= conn->request_len); to: assert(conn->data_len >= conn->request_len && conn->request_len > 1);

Responsible Disclosure

Here at ForAllSecure we take responsible disclosure very seriously because one bad-timed bug can cause a lifetime of ill impact. Before making this post we contacted Netflix and informed them of the bug and the simple way to fix it. The bug was publicly fixed on the Netflix GitHub with the commit d1b1aa26. Credit for the reference was also published in their reference repository at nflx-2019-003.md. After confirming the fix had been pushed, we created this blog post.

Credit Where Credit is Due

Finding and triaging this bug would have not been possible without the hard work that engineers put into making Mayhem awesome and without my fellow coworker Paul Emge, who spent as much time as I did on this bug.

Other Sources

The discovery of this bug is also mentioned in Security Boulevard , ForAllSecure Blog, and Axios.

The CVE is listed as CVE-2019-10028.