Python 2.7.12 mmap information leak

During the end of august I made an audit to the C modules in the popular Python library, version 2.7.12. This audit quickly produced the 1st vulnerability I found in a high-profile library, the 1st of many more that came afterwards.

Python Mmap Module

mmapmodule.c is a C implementation for mapping files. The implementation is in C since most of the needed code is OS specific (Linux / Windows), a fact that opens the way for implementation vulnerabilities that Python is “immune” to.

Mmap Vulnerability

During the resize() operation, the read/write file position is NOT updated to reside in the new limits of the file. This means that the following operations will trigger a fragile inner state:

  1. mmap a file of size 4096 bytes
  2. seek(4096)
  3. resize(2048)

The position will stay at 4096, and now all of the read/write operations are vulnerable to over-read/write.

Actual Effects – Write

The write() operation checks that pos < size, meaning that no writing out-of-bounds could be done. Buffer over-write can not be achieved.

Actual Effects – Read

There are several read operations, some of them are vulnerable to this fragile state:

mmap_read_line_method:

eol = memchr(start, '\n', self->size - self->pos);
if (!eol)
    eol = eof;
else
    ++eol;   /* we're interested in the position after the newline. */
result = PyString_FromStringAndSize(start, (eol - start));
// EI: finding '\n' after INF will cause:
// EI: pos > size
// EI: pos is way outside the mmap range 🙂 [26/08/2016]
self->pos += (eol - start);

mmap_read_method:

// EI: use of assert() enables read after damaged resize() [26/08/2016]
assert(self->size >= self->pos);
n = self->size - self->pos;
/* The difference can overflow, only if self->size is greater than
* PY_SSIZE_T_MAX.  But then the operation cannot possibly succeed,
* because the mapped area and the returned string each need more
* than half of the addressable memory.  So we clip the size, and let
* the code below raise MemoryError.
*/
if (n < 0)
n = PY_SSIZE_T_MAX;
if (num_bytes < 0 || num_bytes > n) {
num_bytes = n;
}
result = Py_BuildValue("s#", self->data+self->pos, num_bytes);
self->pos += num_bytes;

Read Conclusion

This fragile state can enable an attacker to read past the memory-mapped file. Reading to the end of the allocated page might enable an attacker to continue on reading, in case there is another allocated page right after our file. This enables an information leak that will escape the python’s interpreter, as demonstrated in my exploit:

mmap_info_leak_poc

Vulnerability Fix

This vulnerability was fixed in the latest python 2.7.x and 3.y versions. The developers chose to fix the checks in the read_X functions, and the position will still be “floating” in a fragile state. Not a robust inner state, however this is probably the “common” behavior in other file-like modules, and python shouldn’t be an exception.

Author: Eyal Itkin

Former white hat security researcher.

Leave a comment