Safeguard
Application Security•

CWE-770 Allocation of Resources Without Limits or Throttling

Any server that lets a client decide how much memory, CPU or how many files it uses can be knocked over. Learn the pattern and the layered limits that stop it.

Safeguard Security Team
5 min read

CWE-770 is one of the most common routes to denial of service. The product allocates a reusable resource, or a group of them, on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated.

APIs, upload handlers, parsers, message queues and anything that accepts a count or size from a client are in scope. It also has specific children, such as CWE-774 for file descriptors.

How it happens

Somewhere, a number controlled by the caller decides how much the server spends: a length field, a page size, the number of parts in a request, a connection count. If nothing caps it, the caller picks a large value. The record's examples:

  • CVE-2019-19911: a chain in a Python library that did not limit resources used to process images specifying a very large number of bands, leading to excessive memory use or an integer overflow.
  • CVE-2009-4017: a language interpreter that did not restrict how many temporary files it created when handling a MIME request with a large number of parts.
  • CVE-2009-2726: a driver that did not use a maximum width with sscanf style functions, causing stack consumption.
  • CVE-2009-2540: a large integer value for an object length property caused a large amount of memory allocation.
# Vulnerable: the client chooses how much memory the server reserves
@app.route("/buffer")
def buffer():
    n = int(request.args["size"])
    return bytearray(n)

What an attacker gains

Availability, through CPU, memory and other resource consumption. An attacker can prevent other systems, applications and processes from using the same type of resource. Cost is lopsided: a small request can trigger a large spend, so one client can hurt many users.

How to find it

MITRE lists several methods, each with caveats.

  • Manual static analysis can find it but may not reach full coverage in limited time. If denial of service is not a major risk for you, spend effort elsewhere.
  • Fuzzing is aimed at low level bugs, but can inadvertently find uncontrolled allocation, particularly when the fuzzer sends many cases without restarting the target.
  • Automated dynamic analysis can reveal side effects of uncontrolled allocation for resources like processes, memory and connections, by generating a large number of requests.
  • Automated static analysis has limited use here, apart from missing release of program independent system resources, and tools may need special tuning.

A practical test: for every parameter that influences size, count or frequency, send an absurdly large value to a staging copy and watch memory and CPU.

Click through it at your own pace, or open it full screen.

How to fix it

MITRE's mitigations, in plain steps:

  1. Specify limits in requirements. State minimum and maximum expectations, and what should happen when allocation reaches a limit.
  2. Limit what unprivileged users can reach. Set per user limits and let administrators configure them, while avoiding the lockout problem described in CWE-410.
  3. Build throttling into the architecture. The best protection caps what an unauthorized user can cause to be spent. Track request rates per user and block those over a threshold. Protect login especially, and reduce database load, for instance by caching result sets.
  4. Validate input with an allowlist. Treat all input as hostile, accept known good values, and check length, type, range, missing or extra inputs and business rules. Do not rely only on denylists.
  5. Repeat client side checks on the server, because attackers bypass the client.
  6. Pick a response to exhaustion. Either deny a user further access for a time with increasing delays, or throttle all requests uniformly. MITRE admits both have downsides: the first may let an attacker lock out a real user they impersonate, and the second only raises the cost for the attacker.
  7. Place scale limits in protocols.
  8. Fail gracefully. If the program must fail, fail closed, because an attacker may take control during a messy failure, and an uncontrolled failure can cascade. Make sure resource allocation failures leave the system in a safe posture.
  9. Use OS and environment limits where available.
# Fix: bounded input and a hard cap
MAX = 1_000_000
@app.route("/buffer")
def buffer():
    n = int(request.args.get("size", "0"))
    if not 0 < n <= MAX:
        abort(400)
    return bytearray(n)

Quick check

Why is a denylist a poor defense here?

It misses values you did not think of. An allowlist of valid sizes and counts rejects everything outside the specification.

What is the downside of blocking a heavy user after a threshold?

An attacker pretending to be a legitimate user could get that user locked out. Pair blocking with strong authentication.

Should client side limits be enough?

No. Attackers change or skip the client, so repeat every check on the server.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.