Loop bound injection

Excessive iteration caused by a user-controlled length value

Description

Using a user-controlled object's length directly as a loop bound lets an attacker supply a very large value, such as 1e12 or 1e100. The resulting long or effectively endless loop may exhaust CPU or memory and deny service. For example, code expecting an array may receive { length: 1000000000000 } and iterate far longer than intended.

Potential impact

  • Excessive iteration may delay requests or make the server unresponsive.
  • A long loop may occupy CPU time needed by other requests.
  • Operations such as push or concat inside the loop may exhaust memory and crash the process.
  • Blocking the event loop may increase response times and trigger load-balancer or gateway timeouts.

Remediation

  • Check for an actual array with Array.isArray before reading its length.
  • Reject arrays exceeding a fixed application limit, MAX_ITEMS, before copying or iterating over them.
  • If array-like objects are accepted, require a finite, nonnegative integer length and enforce MAX_ITEMS before allocation or conversion.
  • Do not use Array.from(obj).slice(0, MAX_ITEMS) as an input limit: the conversion processes the whole collection before slicing it.
  • Limit request size and validate each element for the intended operation.

Examples

Before

javascript
const express = require("express");
const app = express();
app.use(express.json());

// Use an untrusted length directly as the loop bound
app.post("/sum", (req, res) => {
  const list = req.body.items; // Fully controlled by the requester
  let total = 0;

  // BAD: { items: { length: 1e12 } } causes excessive iteration
  for (let i = 0; i < list.length; i++) {
    total += Number(list[i] || 0);
  }

  res.json({ total });
});

app.listen(3000);

After

javascript
const express = require("express");
const app = express();
app.use(express.json());

const MAX_ITEMS = 10000; // Choose a limit appropriate to the operation

// Validate the type and length before iterating over a bounded array
app.post("/sum", (req, res) => {
  const list = req.body.items;

  // 1) Require an actual array
  if (!Array.isArray(list)) {
    return res.status(400).json({ error: "items must be an array" });
  }

  // 2) Enforce the length limit before iteration or copying
  if (list.length > MAX_ITEMS) {
    return res.status(400).json({ error: "too many items" });
  }

  let total = 0;
  for (const v of list) {
    total += Number(v || 0);
  }

  res.json({ total });
});

app.listen(3000);

The first example trusts the request body's items.length as its loop bound. The second requires an actual array and rejects excessive length before copying or iteration. Separately validate each element's allowed type and range, beyond the illustrated numeric conversion, and limit request size.

References