Skip to content

Buffer Overflow on Unbounded HTTP Body in Gather Logic #196

@yhrscholar

Description

@yhrscholar

Description

Japronto’s HTTP parser (cffiparser.py) will accept a Content-Length or chunked payload of arbitrary size, copy the entire request body into a Python buffer, and then hand it off in one piece to the C “gather” logic (cprotocol.c). That logic uses a fixed-size allocation (malloc(sizeof(PyBytesObject) + GATHER_MAX_LEN)) but does not bound the incoming length. When the body length (or the total decoded chunked length) exceeds GATHER_MAX_LEN, the subsequent memcpy overruns the heap buffer, leading to a crash or potential arbitrary‐write.

# cffiparser.py 

# 1. Parse a large Content-Length header
raw = ffi.buffer(header.value, header.value_len)
self.content_length = int(raw[:])  

# 2. When the body is complete (non-chunked)
body = memoryview(self.buffer)[: self.content_length]
self.on_body(ffi.from_buffer(body))

# 3. Or, after decoding chunked
if self.transfer == 'chunked' and decode_complete:
    body = memoryview(self.buffer)[: self.chunked_offset[0]]
    self.on_body(ffi.from_buffer(body))
/* cprotocol.c */
// Allocate a fixed buffer regardless of requested size
static inline PyBytesObject*
Bytes_FromSize(size_t size) {
    PyBytesObject* result;
    if (!(result = malloc(sizeof(PyBytesObject) + GATHER_MAX_LEN)))
        return (PyBytesObject*)PyErr_NoMemory();
    result->ob_size = (Py_ssize_t)size;
    return result;
}

// Copy incoming data chunk – overflow if len > GATHER_MAX_LEN
static Py_ssize_t
Protocol_on_body(Protocol *self, const char *data, size_t len, size_t tail_len) {
    PyBytesObject *response = Bytes_FromSize(len);
    memcpy(PyBytes_AS_STRING(response), data, len);
    // ...
}

// Later, merging all pieces – overflow if total > GATHER_MAX_LEN
static PyObject*
Gather_flush(Gather *gather) {
    PyBytesObject *buf = Bytes_FromSize(gather->len);
    size_t offset = 0;
    for (size_t i = 0; i < gather->responses_end; i++) {
        PyObject *item = gather->responses[i];
        memcpy(buf->ob_sval + offset,
               PyBytes_AS_STRING(item),
               Py_SIZE(item));
        offset += Py_SIZE(item);
        Py_DECREF(item);
    }
    return (PyObject*)buf;
}

Steps to Reproduce

Use a simple Python script or curl to send a request with a very large Content-Length

Expected Results

The server should enforce a maximum body size (e.g., reject with 413 Payload Too Large) or stream chunks without accumulating everything in RAM.

Under no circumstances should an attacker‐controlled length lead to a buffer overflow in C.

Actual Results

The process crashes with a segmentation fault (heap corruption) when the body length (or total chunked length) exceeds GATHER_MAX_LEN.

Version

commit hash: e73b76e

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions