input with no bounds on size or range
Unconstrained Inputs
Validation that checks type but not magnitude. An input with no upper bound on length, size, or value becomes unbounded memory, catastrophic backtracking, integer overflow, or a denial of service. Constrain the range, not just the shape.
Nothing limits how big, how long, or how extreme an input can be — so something downstream pays for it.
01in the wild
In the wild
No Bound on Size
An input with no maximum length is a denial-of-service waiting for a big enough request.
example.js
// WRONG: a catastrophic-backtracking regex on unbounded input
const re = /^(a+)+$/;
re.test("aaaaaaaaaaaaaaaaaaaaaaaa!"); // hangs the event loop (ReDoS)
// RIGHT: bound the input first, then use a linear matcher
if (input.length > 256) throw new Error("too long");
const re = /^a+$/;Nested quantifiers make match time exponential in input length. Capping length (and avoiding the pattern) keeps one request from freezing the server.
// observed
unbounded: one request pins the CPU at 100% bounded: rejected at 256 chars, linear match
example.py
# WRONG: read the whole upload into memory
data = request.body.read() # a 10GB POST is now 10GB of RAM
# RIGHT: enforce a limit before you ever allocate
MAX = 5 * 1024 * 1024
data = request.body.read(MAX + 1)
if len(data) > MAX:
raise ValueError("payload too large")An unconstrained read trusts the client to be reasonable. A hard cap turns a memory-exhaustion attack into a clean rejection.
// observed
unbounded: OOM kill under a large upload bounded: 'payload too large' at 5MB
No Bound on Range
A number that type-checks can still be negative, zero, or astronomically large.
example.py
# WRONG: limit is an int, so it must be fine... right?
def paginate(items, limit):
return items[:limit] # limit=-1 silently drops the last row
# limit=10**9 tries to slice everything
# RIGHT: constrain the range at the boundary
def paginate(items, limit):
limit = max(1, min(int(limit), 100))
return items[:limit]Type checking proved limit is an integer; it never proved limit is sane. Clamp to a valid range before use.
// observed
limit=-1: drops a row, no error clamped: limit forced into 1..100
example.go
// WRONG: trust a count from the wire, then allocate it
n := req.Count // int from an untrusted client
buf := make([]byte, n) // n negative -> panic; n huge -> OOM
// RIGHT: validate the range before allocating
if n < 0 || n > maxCount {
return fmt.Errorf("count out of range: %d", n)
}
buf := make([]byte, n)make() with a hostile size panics or exhausts memory. A range check is the difference between a rejected request and a crashed process.
// observed
unchecked: panic: makeslice: len out of range
checked: error('count out of range: -1')02cross-pollination
Where this compounds
Runtime Errors
- Unbounded Input → Quadratic Blowup × for-Loop Control Flow
Data Corruption
- Schema Drift / Mixed-Version Write × File & Network Access
- Web Cache Poisoning × Cache Invalidation
- Unframed Stream Corruption × Async IO Misuse
- Stack Smash via Unbounded Recursion × Stack Overflow Bugs
03weakness catalog
Mapped weaknesses (CWE)
On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.