RC RANDOM CHAOS

The browser runs whatever the host returns

A browser tab holding 2 GB is not a malfunction; it is a trust model that resolves references and never revalidates the referent behind the name.

· 8 min read
The browser runs whatever the host returns

The IBM PC placed a hard ceiling at 640 KB of conventional memory. Everything a program could touch had to fit under that line, and the operator managed the line by hand. CONFIG.SYS and AUTOEXEC.BAT were where you did the accounting. HIMEM.SYS and EMM386 opened the extended and upper memory areas, DEVICEHIGH and LOADHIGH pushed drivers into the upper memory blocks, and you counted the bytes you freed the way you would count change. Nothing loaded that you had not personally admitted. The budget was fixed, visible, and enumerated, and the system could not exceed it because there was nothing behind the line to exceed it with.

A browser tab is a different kind of system, and it is worth being precise about what kind. It is not a document viewer that happens to run code. It is an execution environment whose defining behavior is the resolution of references. When it parses a page and reaches a script src attribute, it issues an HTTP request to the named host and executes whatever bytes that host returns at request time, inside the same tab, with the tab’s resources. It does not ask what the code will do. It does not ask what the code will cost. It resolves the reference and runs the referent. That is the specified behavior, documented and stable across every browser that implements the HTML and HTTP standards.

So when one tab holds 2 GB of resident memory to render a page about socks, nothing has malfunctioned. The browser was never handed a ceiling to defend. It was handed references to resolve, and it resolved them. The 640 KB machine and the 2 GB tab are not two points on a line of progress. They are two different trust models. One enumerated everything it admitted before admitting it. The other admits whatever a reference resolves to, and discovers the cost only after the code is already running.

The assumption underneath the second model is simple and almost never stated. It is that a reference names a known thing. HTTP carries no notion of a resource budget, and the src attribute carries no notion of what sits behind the URL beyond the URL itself. The model assumed that naming a resource was equivalent to knowing it, that the act of writing a reference into a page was a decision made against a fixed and understood referent. Trust was placed in the identifier, and the identifier was treated as a guarantee of everything that would follow from resolving it.

The web’s actual trust boundary was drawn somewhere else entirely. The same-origin policy governs which origin can read which data. It says nothing about how much a referenced script may consume, nothing about the identity of the parties a reference will ultimately pull in, nothing about whether the thing behind a URL today is the thing that was behind it when the reference was written. Origin was the axis the model chose to police. Content and cost and downstream identity were left outside the boundary, because the assumption held that a trusted reference implied a bounded and stable referent. Trust was delegated to the name, and the name was assumed to hold still.

The DOS model made the opposite assumption, or rather refused to make one. Trust there was enumerated, not delegated. The 640 KB was validated state, recomputed every time a driver was added, because the operator did not accept that a component loaded today would keep its cost or its behavior tomorrow without accounting. The web assumed persistence and transferability: that a reference was durable across time, and that whatever trust was granted to it at authoring would transfer, unexamined, to every future resolution. That was the load-bearing assumption. It was never validated. It was inherited.

What changed was not attacker capability, and it was not anyone’s competence or diligence. What changed was the validity of the assumption. The syntax held perfectly. A script src still resolves a URL, exactly as specified, exactly as it did when a page pulled in a single analytics file from a single host. The reference did not change. What changed is that the referent stopped being a fixed thing, and the model had no mechanism to notice, because it had never treated the referent as something to be re-checked.

In practice, the single reference now resolves to a tag manager such as Google Tag Manager, and the tag manager resolves to further references, and some of those enter real-time bidding under protocols like OpenRTB, where the parties who will execute code inside the tab are selected by auction at request time. One src attribute, written once, admits 17 companies that the page never named. The author enumerated one reference. The system resolved to a population, and the composition of that population is decided after the page loads, by mechanisms the page has no visibility into and no authority over. The identifier still names something. What it names is no longer knowable at the moment the trust is granted.

And the system did not re-evaluate trust when this happened, because re-evaluation was never part of its behavior. It inherited trust from the reference. The decision to trust was made once, at the moment the tag was placed, against a referent that was assumed to be stable, and everything that referent later resolved to arrived carrying that original grant. The assumption was that naming a thing was knowing it. That assumption no longer holds. The reference is resolved. The state behind it is not validated.

The mechanism is not concealment. Nothing hides. Watch the tab’s network activity and every request is visible, every host is named, every response is logged. What is absent is not observability of the traffic. What is absent is any point at which the system asks whether the referent still matches the reference. The browser receives a script from the host named in the src attribute, confirms the transport under TLS, confirms the origin under the same-origin policy, and executes. Each of those checks answers a question about the source. None of them answers a question about the content or the cost. The identity of the host stands in for the integrity of what the host returns.

Validation was replaced by resolution, and the substitution is exact. To validate is to compare an artifact against a known-good state and act on the result. To resolve is to retrieve whatever a name currently points at and proceed. The browser resolves. When Google Tag Manager returns a container, the browser does not compare that container to a prior version, to a declared behavior, to a memory budget, or to a list of parties it is permitted to admit. It runs the container, and the container issues its own resolutions, and those resolve into an OpenRTB auction whose winners are selected after the page has loaded. At no step does the system re-check the grant it made at the first reference. The grant was made once. Everything downstream inherits it.

This is why 2 GB is not a fault. A fault is a departure from specified behavior. The browser executed exactly what its specification requires: it resolved references and ran referents in the order they arrived. The memory was consumed by code the system was built to run the instant it was named. No control was bypassed, because no control stood between the reference and the referent. The same-origin policy did what it specifies, which is to isolate what one origin can read from another. It was never the thing that would count the parties or bound the cost, and it did not behave as though it were. The outcome the operator sees, one tab holding the resident memory of a small application in order to display a page about socks, is the specification running correctly against an assumption that no longer describes the referent.

The pattern underneath this is small, and it is everywhere. A system grants trust to a name, resolves the name at the moment of use, and executes whatever the resolution returns, without re-checking that the thing returned is the thing the trust was granted against. Trust attaches to the reference. The referent is free to change. Between the moment the reference is written and the moment it is resolved, nothing in the system is obligated to notice that these are no longer the same thing. Execution follows the reference, and verification of the referent was never in the path.

Domain Name System resolution is the same mechanism operating one layer down. A hostname written into a configuration file is a reference, fixed once by whoever wrote it, against an endpoint they assumed. When the system connects, it does not verify that endpoint. It queries an authoritative nameserver and connects to whatever address that server returns at query time. The name is stable. The address behind it is decided elsewhere, by a party the author of the configuration does not control, and it can change between one query and the next while the configuration file, the reference itself, remains byte for byte identical. The system trusts the name and connects to the answer. The answer is authored after the trust is granted. This is the same shape as the src attribute resolving into an auction: a durable reference, a mutable referent, and no revalidation between them.

The two cases are not analogous. They are the same operation. In both, the identifier is treated as a guarantee of what sits behind it, and in both, the thing behind it is composed at resolution time by parties outside the reach of whoever wrote the reference. The 640 KB machine could not enter this failure mode, not because it was simpler, but because it validated state rather than resolving names. It recomputed what it admitted each time it admitted something. A system that resolves references does not recompute. It looks up, and it runs, and the distance between what a name meant when it was written and what it returns when it is used is exactly where the referent goes to change.

A system that trusts references resolves trust once. It does not revalidate. Every state after the first inherits a decision made against a state that no longer exists.

The 640 KB was a budget the operator could see and enumerate. The 2 GB is a budget no one set, because the model that produced it was never built to hold a line. It was built to resolve names, and it resolves them faithfully, into whatever they now return.

The reference is honored. The referent is not examined. The control exists. The outcome does not.

Share

Keep Reading

Stay in the loop

New writing delivered when it's ready. No schedule, no spam.