Double Free in Adonisjs with Mutual Tls
Double Free in Adonisjs with Mutual Tls — how this specific combination creates or exposes the vulnerability
A Double Free occurs when a program attempts to free the same block of memory more than once. In AdonisJS, this typically arises in request lifecycle handling where resources—such as TLS session objects, request context, or custom transport state—are released multiple times due to asynchronous completion handlers or error-path branching. When Mutual TLS (mTLS) is enabled, the server performs additional handshake validation and maintains per-connection certificate metadata and secure session state. If AdonisJS application code or framework-level integration does not correctly coordinate ownership of these mTLS-derived objects across asynchronous steps (e.g., certificate verification callback, TLS session teardown, and downstream request disposal), the same underlying memory can be freed both during normal TLS shutdown and again during an error or retry path.
With mTLS, each connection presents a server-side certificate and a client certificate. AdonisJS does not manage TLS itself but relies on the underlying Node.js tls module. The risk emerges when application code wraps or extends the TLS session handling—for example, by attaching custom objects to the socket or tlsSocket—and then frees those objects in multiple callbacks (e.g., secureConnect, end, and an error handler). Because mTLS adds extra certificate material and potentially duplicated references to session context, the probability of mismatched ownership and premature double release increases. This can corrupt memory, lead to use-after-free patterns, or cause unpredictable behavior, especially under high concurrency or when clients renegotiate connections.
The exposure is specific to the combination because mTLS introduces additional per-connection state that must be explicitly managed. Standard HTTP request handling in AdonisJS may already have nuanced cleanup paths; adding mTLS amplifies the surface if developers inadvertently bind cleanup logic to both the TLS layer and the application layer. For instance, if an application subscribes to tlsSocket.on('secureConnect', ...) and also listens on the HTTP request’s close or end events, both may attempt to release the same helper object (e.g., a wrapped certificate store or session metadata). The framework’s unauthenticated scan capability can detect indicators such as missing validation on client certificates or inconsistent error handling, but it does not alter runtime behavior; developers must ensure deterministic, single ownership and release semantics.
Mutual Tls-Specific Remediation in Adonisjs — concrete code fixes
Remediation centers on ensuring a single, clear ownership path for any object derived from or attached to the mTLS session. Do not attach mutable state to TLS socket objects and free it in multiple places; instead, centralize cleanup in one deterministic location. Below are concrete patterns for AdonisJS applications that use mTLS via the Node.js tls module, assuming an HTTPS server created with tls.createServer and integrated into AdonisJS.
const tls = require('tls');
const fs = require('fs');
const http = require('http');
const options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
ca: [fs.readFileSync('ca-cert.pem')],
requestCert: true,
rejectUnauthorized: true,
};
const server = tls.createServer(options, (tlsSocket) => {
// Attach lightweight metadata only if necessary, and use a WeakMap to avoid ownership cycles
const metadata = {
certFingerprint: tlsSocket.getPeerCertificate().fingerprint,
establishedAt: Date.now(),
};
// Use a WeakMap to associate metadata without preventing GC
if (!tlsSocket.__meta) {
tlsSocket.__meta = new WeakMap();
}
tlsSocket.__meta.set(tlsSocket, metadata);
tlsSocket.once('close', () => {
// Single cleanup point: release resources tied to this socket
if (tlsSocket.__meta && tlsSocket.__meta.has(tlsSocket)) {
tlsSocket.__meta.delete(tlsSocket);
}
});
// Example: integrate with an AdonisJS-style request handler
const req = http.request({
method: 'GET',
hostname: 'localhost',
port: 3333,
socket: tlsSocket,
});
req.on('response', (res) => {
// Process response
res.on('end', () => {
// Do not free metadata here; rely on 'close' above
});
});
req.end();
});
server.listen(8443, () => {
console.log('mTLS server listening on port 8443');
});
If you are using AdonisJS’s HTTP server integration, ensure that any middleware or event handlers that interact with the TLS layer avoid double-release patterns. For example, do not manually destroy a socket in both an error handler and the end event:
server.on('secureConnect', function() {
const cert = this.getPeerCertificate();
// Validate certificate fields here
if (!cert || !cert.fingerprint) {
this.destroy(); // OK if this is the only cleanup
}
});
server.on('error', (err) => {
// Do not call this.destroy() again for the same socket already handled in 'secureConnect'
logger.error('TLS error', err);
});
Additionally, when using the middlebrick CLI to scan your AdonisJS endpoints, you can validate that mTLS is enforced and that endpoints do not expose unauthenticated attack surfaces. The scan will highlight findings related to authentication and encryption, but it does not alter runtime behavior; you remain responsible for ensuring cleanup discipline. For teams needing continuous visibility, the Pro plan supports continuous monitoring of scanned APIs, and the GitHub Action can gate CI/CD if risk scores exceed your defined thresholds.