Double Free in Express (Javascript)
Double Free in Express with Javascript
In Node.js applications that use Express, a double free vulnerability can arise when memory allocated for HTTP request handling is released twice due to improper error handling or resource cleanup. This typically occurs when an asynchronous callback or middleware triggers an error after an object (such as a response or stream) has already been sent or destroyed, leading to a second attempt to free the same resource. Because Express processes requests through a chain of middleware and route handlers, a single request may involve multiple internal objects that are not always cleaned up predictably, especially when errors occur late in the lifecycle.
For example, consider a route handler that initiates a database query and writes directly to the response stream without proper error checking. If the database operation fails after data has been partially written, the application may attempt to destroy the response stream again in an error handler, even though it was already flushed or closed. This can corrupt internal state or trigger a double free condition in the underlying event loop or TCP socket handling. While Node.js abstracts much of the memory management, the Express layer inherits risks from improper stream or object lifecycle management.
These issues are often exacerbated in high-throughput environments where concurrent requests share similar code paths. Without explicit validation of object states before cleanup, developers may inadvertently create conditions where resources are freed more than once. The vulnerability does not manifest as a remote code execution vector but can lead to application crashes or denial of service through process instability. Because Express does not enforce strict object lifecycle rules, such vulnerabilities depend heavily on developer practices around error propagation and resource disposal.
Real-world examples include cases where developers reuse response objects across middleware without checking if they have already been sent, or where error handlers assume a stream is still active when it may have already been closed by an earlier handler. These patterns are subtle but can be exploited or trigger crashes under load. Proper error handling, early returns, and state tracking are essential to mitigate risks in Express applications.
Javascript-Specific Remediation in Express
To prevent double free conditions in Express, developers must ensure that objects such as request, response, and stream instances are not accessed after they have been sent or destroyed. Use early returns and explicit state checks to avoid re-entering cleanup logic. Below is a correct implementation that avoids double free by tracking whether the response has already been sent.
const express = require('express');
const app = express();
app.get('/user', async (req, res) => {
let responseSent = false;
const sendUser = (user) => {
if (responseSent) return;
responseSent = true;
res.json(user);
};
const fetchUser = async () => {
try {
const user = await db.getUser(req.query.id);
sendUser(user);
} catch (err) {
if (!responseSent) {
res.status(500).json({ error: 'Internal server error' });
}
}
};
fetchUser();
});
app.listen(3000);
In this example, the responseSent flag ensures that the response is only sent once, even if multiple code paths attempt to write to it. This prevents scenarios where an error handler tries to write to the response after it has already been flushed. Additionally, avoid reusing res objects across middleware without validation. Always check the sent state before performing operations that might trigger cleanup logic.
Another best practice is to use streams safely by listening for 'error' events and ending them properly. Never call res.end() or res.destroy() multiple times. Instead, centralize stream termination logic within a single handler or use res.on('finish') to manage cleanup safely. These patterns reduce the risk of double free by ensuring deterministic object lifecycle management.