Skip to content

Security Advisory: Unvalidated declaration name in CSSStyleDeclaration.setProperty overwrites the internal length field, causing an uncatchable out-of-memory abort #119

Description

@waydeshi

Summary

Attribute Value
Vendor / Org NV (Nikita Vasilyev)
Product cssom
Component lib/CSSStyleDeclaration.js
Affected Versions <= 0.5.0 (all published versions; verified on 0.3.1 and 0.5.0)
Severity High
CVSS 3.1 Score 7.5 (High)
CVSS 3.1 Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE CWE-1284 (Improper Validation of Specified Quantity in Input)
Affected File lib/CSSStyleDeclaration.js:41-56 (write), :112-124 (serialization loop)

Description

Improper validation of a CSS declaration name in cssom <= 0.5.0 lets a remote unauthenticated
caller abort the Node.js process via a 20-byte stylesheet passed to CSSOM.parse().
CSSStyleDeclaration.setProperty writes each declaration name straight onto the instance with
this[name] = value, in the same namespace as the object's own length field that tracks the
declaration count. A declaration literally named length overwrites that count with an
attacker-chosen number. The cssText serializer then loops for (i=0; i < this.length; ++i) and
pre-sizes an array to the poisoned bound, so a{length:2000000000} makes the library try to build
a two-billion-entry array and V8 aborts with an uncatchable out-of-memory error. The attacker
turns the ability to submit a tiny CSS string into a reliable crash of the shared service that
parses it.


Impact

A remote unauthenticated caller who can submit CSS to a service that parses it with cssom can
terminate the process with a fixed 20-byte string. Because the failure is a V8 fatal OOM rather
than a thrown error, try/catch around parse() does not save the consumer, so the service
crashes for every concurrent user. This is a denial-of-service caused by an input-validation flaw
in a specific internal field, distinct from generic "large input parses slowly" behavior: the
input is tiny and the amplification comes from an overwritten control variable. No existing CVE
covers cssom (OSV.dev and NVD return no records for the package).


Remediation

Recommended Fix

In setProperty, reject or divert declaration names that collide with internal fields, and store
declarations in a dedicated null-prototype map instead of the instance namespace:

setProperty: function(name, value, priority) {
	if (name === "length" || name === "parentRule" || name === "_importants") {
		return; // never let a declaration name overwrite an internal field
	}
	// ... existing logic ...
}

Additionally coerce the serialization bound so a poisoned value cannot drive allocation:

for (var i = 0, length = this.length >>> 0; i < length; ++i) { ... }

This is an in-repo change that protects consumers using the documented CSSOM.parse /
CSSStyleDeclaration API correctly. It does not reduce to "callers should sanitize their CSS":
the crash is triggered by grammatically ordinary CSS that the parser is expected to accept, and
no documented precondition tells callers to strip a declaration named length.

Workaround

Consumers can cap input length and run CSSOM.parse in a worker/child process with a memory
limit so an OOM abort does not take down the main process. This does not prevent the crash of the
worker.


References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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