Chapter 4. NULL Pointer FTW

Note

Saturday, January 24, 2009

Dear Diary,

I found a really beautiful bug today: a type conversion vulnerability leading to a NULL pointer dereference (see Section A.2). Under normal circumstances this wouldn’t be a big deal, since the bug affects a user space library, which generally means that at worst it would crash a user space application. But this bug is different from the average user space NULL pointer dereferences, and it’s possible to exploit this vulnerability to execute arbitrary code.

The vulnerability affects the FFmpeg multimedia library that is used by many popular software projects, including Google Chrome, VLC media player, MPlayer, and Xine to name just a few. There are also rumors that YouTube uses FFmpeg as backend conversion software.[37]

Note

There are other examples of exploitable user space NULL pointer dereferences. See Mark Dowd’s MacGyver exploit for Flash (http://blogs.iss.net/archive/flash.html) or Justin Schuh’s Firefox bug (http://blogs.iss.net/archive/cve-2008-0017.html).

4.1 Vulnerability Discovery

To find the vulnerability I did the following:

  • Step 1: List the demuxers of FFmpeg.

  • Step 2: Identify the input data.

  • Step 3: Trace the input data.

Step 1: List the Demuxers of FFmpeg

After getting the latest source code revision from the FFmpeg SVN repository, I generated a list of the demuxers that are available in the libavformat library, which is included with FFmpeg (see Figure 4-1). I noticed that FFmpeg separates most demuxers in different C files under the directory libavformat/.

FFmpeg libavformat demuxers

Figure 4-1. FFmpeg libavformat demuxers

Note

FFmpeg development has moved to a Git repository,[38]and the SVN repository is no longer updated. The vulnerable source code revision (SVN-r16556) of FFmpeg can now be downloaded from this book’s website.[39]

Step 2: Identify the Input Data

Next, I tried to identify the input data processed by the demuxers. While reading the source code, I discovered that most demuxers declare a function called demuxername_read_header(), which usually takes a parameter of the type AVFormatContext. This function declares and initializes a pointer that looks like this:

[..]
ByteIOContext *pb = s->pb;
[..]

Many different get_something functions (e.g., get_le32(), get_buffer()) and special macros (e.g., AV_RL32, AV_RL16) are then used to extract portions of the data pointed to by pb. At this point, I was pretty sure that pb had to be a pointer to the input data of the media files being processed.

Step 3: Trace the Input Data

I decided to search for bugs by tracing the input data of each demuxer at the source code level. I started with the first demuxer file from the list, called 4xm.c. While auditing the demuxer of the 4X movie file format,[40] I found the vulnerability shown in the listing below.

Source code file

libavformat/4xm.c

Function

fourxm_read_header()

[..]
 93    static int fourxm_read_header(AVFormatContext *s,
 94                                  AVFormatParameters *ap)
 95    {
 96      ByteIOContext *pb = s->pb;
 ..
101      unsigned char *header;
 ..
103      int current_track = −1;
 ..
106      fourxm->track_count = 0;
107      fourxm->tracks = NULL;
 ..
120       /* allocate space for the header and load the whole thing */
121       header = av_malloc(header_size);
122       if (!header)
123           return AVERROR(ENOMEM);
124       if (get_buffer(pb, header, header_size) != header_size)
125           return AVERROR(EIO);
 ..
160      } else if (fourcc_tag == strk_TAG) {
161          /* check that there is enough data */
162          if (size != strk_SIZE) {
163              av_free(header);
164              return AVERROR_INVALIDDATA;
165          }
166          current_track = AV_RL32(&header[i + 8]);
167          if (current_track + 1 > fourxm->track_count) {
168             fourxm->track_count = current_track + 1;
169             if((unsigned)fourxm->track_count >= UINT_MAX / sizeof(AudioTrack))
170               return −1;
171             fourxm->tracks = av_realloc(fourxm->tracks,
172                 fourxm->track_count * sizeof(AudioTrack));
173             if (!fourxm->tracks) {
174               av_free(header);
175               return AVERROR(ENOMEM);
176             }
177          }
178          fourxm->tracks[current_track].adpcm = AV_RL32(&header[i + 12]);
179          fourxm->tracks[current_track].channels = AV_RL32(&header[i + 36]);
180          fourxm->tracks[current_track].sample_rate = AV_RL32(&header[i + 40]);
181          fourxm->tracks[current_track].bits = AV_RL32(&header[i + 44]);
[..]

The get_buffer() function in line 124 copies input data from the processed media file into the heap buffer pointed to by header (see lines 101 and 121). If the media file contains a so-called strk chunk (see line 160) the AV_RL32() macro in line 166 reads an unsigned int from the header data and stores the value in the signed int variable current_track (see line 103). The conversion of a user-controlled unsigned int value from the media file to a signed int could cause a conversion bug! My interest piqued, I continued to search through the code, excited that I might be on to something.

The if statement in line 167 checks whether the user-controlled value of current_track + 1 is greater than fourxm->track_count. The signed int variable fourxm->track_count is initialized with 0 (see line 106). Supplying a value >= 0x80000000 for current_track causes a change in sign that results in current_track being interpreted as negative (to find out why, see Section A.3). If current_track is interpreted as negative, the if statement in line 167 will always return false (as the signed int variable fourxm->track_count has a value of zero), and the buffer allocation in line 171 will never be reached. Clearly, it was a bad idea to convert that user-controlled unsigned int to a signed int.

Since fourxm->tracks is initialized with NULL (see line 107) and line 171 is never reached, the write operations in lines 178–181 lead to four NULL pointer dereferences. Because NULL is dereferenced by the user-controlled value of current_track, it’s possible to write user-controlled data at a wide range of memory locations.

Note

Perhaps you wouldn’t technically call this a NULL pointer “dereference,” since I’m not actually dereferencing NULL but a nonexistent structure that’s located at a user-controlled offset from NULL. In the end it depends on how you define the term NULL pointer dereference.

The expected behavior of FFmpeg is shown in Figure 4-2 as follows:

  1. fourxm->tracks is initialized with NULL (see line 107).

  2. If the processed media file contains a strk chunk, the value of current_track is extracted from the user-controlled data of the chunk (see line 166).

  3. If the value of current_track + 1 is greater than zero, a heap buffer is allocated.

  4. The heap buffer pointed to by fourxm->tracks is allocated (see lines 171 and 172).

  5. Data from the media file is copied into the heap buffer, while current_track is used as an array index into the buffer (see lines 178–181).

  6. When this behavior occurs, there is no security problem.

Expected behavior when FFmpeg operates normally

Figure 4-2. Expected behavior when FFmpeg operates normally

Figure 4-3 shows what happens when this bug affects FFmpeg:

  1. fourxm->tracks is initialized with NULL (see line 107).

  2. If the processed media file contains a strk chunk, the value of current_track is extracted from the user-controlled data of the chunk (see line 166).

  3. If the value of current_track + 1 is less than zero, the heap buffer isn’t allocated.

  4. fourxm->tracks still points to memory address NULL.

  5. The resulting NULL pointer is then dereferenced by the user-controlled value of current_track, and four 32-bit values of user-controlled data are assigned to the dereferenced locations (see lines 178–181).

  6. Four user-controlled memory locations can be overwritten with four user-controlled data bytes each.

Unexpected behavior of FFmpeg causing memory corruption

Figure 4-3. Unexpected behavior of FFmpeg causing memory corruption

What a beautiful bug!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.95.38