CWE-1265 describes code that invokes something believed to be reentrant, where that something makes a call that unintentionally produces a nested invocation of non-reentrant code. The first invocation has not finished when the second begins, and the second changes state the first still relies on.
It hits anything that runs complex or untrusted logic: web browsers, PDF readers, script engines, and any product that executes scripts from sources it does not control.
How it happens
In a complex product, one function call can lead to many code paths, some of them deeply nested. It is hard to foresee every path that can come out of a call, even one assumed to be reentrant. When an external actor can manipulate inputs, they can steer control flow into a wide range of paths.
The weakness is present when one of those paths alters program state that the original caller assumes is unchanged during the call.
Two recorded CVEs show how an attacker reaches such a path.
- CVE-2014-1772: by registering a malicious onerror handler, an adversary can produce unexpected re-entrance of a CDOMRange object.
- CVE-2018-8174: this covers several scenarios made possible by abuse of the Class_Terminate feature in Microsoft VBScript, where one scenario uses it to cause an undesirable re-entrance.
In both, a callback or hook that the attacker controls runs in the middle of an operation.
A simplified picture
This JavaScript sketch is not from either CVE. It only shows the shape of the bug: a function caches state, calls out to a user supplied hook, then keeps using the cache.
// Vulnerable shape: assumes items is unchanged during the callback
function processAll(items, onItem) {
const count = items.length;
for (let i = 0; i < count; i++) {
onItem(items[i]); // hook may call processAll or mutate items
}
}
If onItem removes entries from items, items[i] can go out of range later in the loop. In a memory unsafe language the same pattern becomes an out of bounds access.
A safer shape copies what it needs before calling out, or refuses nested entry.
// Safer shape: snapshot first, and block re-entry
let busy = false;
function processAll(items, onItem) {
if (busy) throw new Error("re-entrant call");
busy = true;
try {
for (const item of [...items]) {
onItem(item);
}
} finally {
busy = false;
}
}
What an attacker gains
MITRE records an integrity impact. Exploitation can leave the application in an unexpected state and cause variables to be reassigned before the first invocation has completed. That may eventually result in memory corruption or unexpected code execution.
How to find it
The record lists no detection methods, so this is mostly code review and testing with hostile callbacks.
- List every place where your code calls out to something it does not own: event handlers, callbacks, destructors, finalizers, plugins, script engines.
- For each one, ask what state the caller keeps across the call and whether the callee could change it.
- Write tests where the callback calls back into the same API, deletes the object, or changes the collection being walked.
- Fuzz script and document inputs for products that run untrusted scripts.
How to fix it
The record gives no mitigation text. The definition suggests these practical steps.
- Do not hold assumptions about state across a call to outside code. Re-read state after the call.
- Take a copy or a snapshot of what you iterate over before calling out.
- Add an explicit guard that rejects nested entry into code that is not designed for it.
- Reduce the places where untrusted scripts can run user code in the middle of internal operations.
- Keep dependencies current. Both recorded examples are vendor product bugs, and the fix arrived as an update.
Try it yourself
Pick one function in your codebase that takes a callback. Write a callback that calls the same function again, and another that mutates the argument. If either breaks the function, you have found a reentrancy hazard.
Quick check
Why is a function that is reentrant in isolation still a risk?
Because the calls it makes can lead to other code that is not reentrant. The danger is the full set of nested paths, not just the first function.
What does the attacker control in the recorded CVEs?
A handler or feature that runs during an operation, such as an onerror handler or Class_Terminate.