Skip to content

nova_request_plugin: read_body/2 accumulates request body with no total size cap (DoS) #397

Description

@Taure

Problem

nova_request_plugin:read_body/2 loops cowboy_req:read_body/1 into an accumulator with no upper bound on the total accumulated size:

read_body(Req, Acc) ->
    case cowboy_req:read_body(Req) of
        {ok, Data, Req0}   -> {<<Acc/binary, Data/binary>>, Req0};
        {more, Data, Req0} -> read_body(Req0, <<Acc/binary, Data/binary>>)
    end.

cowboy's per-read length cap only bounds a single read_body/1 call; the {more, ...} loop defeats it by concatenating every chunk. When a scope enables decode_json_body/read_urlencoded_body, an unauthenticated client POSTing a multi-gigabyte (or deeply nested) body has the whole thing buffered in memory — and then json:decode/1 runs on it — before any later plugin (rate limiter, auth, origin check) can reject the request. Sustained large uploads exhaust node memory.

Impact

Any app exposing an unauthenticated POST that reads the body (login/register/public forms) is exposed. The rate limiter cannot mitigate it because nova_request_plugin runs earlier in the chain.

Suggested fix

Thread a max-total-length through the plugin options and abort accumulation past it, replying 413 Payload Too Large:

read_body(Req, Acc, Max) when byte_size(Acc) > Max -> {error, too_large};
read_body(Req, Acc, Max) ->
    case cowboy_req:read_body(Req) of
        {ok, Data, Req0}   -> {<<Acc/binary, Data/binary>>, Req0};
        {more, Data, Req0} -> read_body(Req0, <<Acc/binary, Data/binary>>, Max)
    end.

with a sane default (e.g. 1 MiB) overridable per scope. Happy to send a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions