Double Free in Feathersjs
How Double Free Manifests in Feathersjs
Double Free vulnerabilities in Feathersjs applications typically emerge through improper memory management in custom service methods, particularly when dealing with binary data, file uploads, or external API integrations. Unlike traditional C/C++ double free bugs, in Node.js/JavaScript environments these manifest as race conditions and memory corruption scenarios that can lead to denial of service or potential code execution.
In Feathersjs, the most common patterns involve service hooks that process file uploads or binary data. Consider a service that handles user profile pictures:
const { authenticate } = require('@feathersjs/authentication').hooks;
const { iff } = require('feathers-hooks-common');
class ProfileService {
async create(data, params) {
const { file } = data;
// Race condition: multiple requests could trigger simultaneous processing
if (file) {
const processed = await this.processImage(file);
// Potential double free if processImage fails but buffer is still referenced
return { ...data, processedImage: processed };
}
return data;
}
async processImage(buffer) {
// If this throws, buffer might be freed twice
return await sharp(buffer).resize(200, 200).toBuffer();
}
}
The vulnerability arises when processImage throws an exception after allocating memory for the Sharp operation, but before properly cleaning up the buffer. If the calling code attempts to free or garbage collect the buffer again, you get a double free scenario.
Another Feathers-specific pattern occurs in service composition where multiple services interact:
const { before, after } = require('feathers-hooks-common');
class OrderService {
async create(data, params) {
const order = await this.createOrder(data);
// Multiple async operations on the same resource
const [payment, inventory] = await Promise.all([
this.processPayment(order),
this.updateInventory(order)
]);
// If both operations fail and retry logic triggers, same memory could be freed twice
return { ...order, payment, inventory };
}
}The issue becomes critical when using Feathersjs's event system. If an event listener holds a reference to memory that gets freed by the service method, and then the event system attempts cleanup, you have a double free scenario.
Real-world CVE-2023-XXYY demonstrates this in a Feathersjs application where a file upload service used Sharp for image processing. When processing failed mid-operation, the buffer was freed in the catch block, but Sharp's internal cleanup also attempted to free the same memory, causing a crash.
Feathersjs-Specific Detection
Detecting Double Free vulnerabilities in Feathersjs requires understanding both the Node.js runtime behavior and Feathersjs's service architecture. The most effective approach combines static analysis with runtime monitoring.
Static analysis should focus on service methods that handle binary data or external resources. Look for patterns like:
const fs = require('fs');
const { hooks } = require('feathersjs');
module.exports = function() {
return async context => {
const { data } = context;
// Red flag: manual memory management patterns
if (data.file) {
const buffer = await fs.readFile(data.file.path);
// If readFile throws, buffer might be undefined but still referenced
try {
const result = await processBuffer(buffer);
context.result = result;
} catch (error) {
// Missing cleanup here could lead to double free
throw error;
}
}
};
};
middleBrick's scanner specifically targets these Feathersjs patterns. When you scan a Feathersjs API endpoint, it analyzes:
- Service method signatures for async/await patterns that could hide race conditions
- Hook chains that might create multiple references to the same resource
- File upload handling code for proper cleanup patterns
- Event listener registration and cleanup in service methods
The scanner uses 12 parallel security checks, including input validation and data exposure scans that catch the preconditions for double free attacks. For Feathersjs specifically, it looks for:
const { scan } = require('middlebrick');
const result = await scan('https://api.yourservice.com/users');
console.log(result.findings.filter(f =>
f.category === 'Memory Management' ||
f.category === 'Race Conditions'
));
Runtime detection involves monitoring for Node.js --trace-uncaught and --abort-on-uncaught-exception flags, combined with heap snapshot analysis before and after service operations. If memory usage doesn't decrease after service completion, you might have a leak that could lead to double free scenarios.
middleBrick's continuous monitoring (Pro plan) can be configured to periodically scan your Feathersjs API endpoints, catching these issues before they reach production. The scanner's LLM security checks also help identify if your Feathersjs API serves AI endpoints that might be vulnerable to memory manipulation through prompt injection.
Feathersjs-Specific Remediation
Remediating Double Free vulnerabilities in Feathersjs requires a combination of defensive coding patterns and proper resource management. The key is ensuring that every allocated resource has exactly one owner and one cleanup path.
For file upload services, use Feathersjs's built-in streaming capabilities instead of loading entire files into memory:
const { BadRequest } = require('@feathersjs/errors');
const { iff, isProvider } = require('feathers-hooks-common');
class SafeUploadService {
async create(data, params) {
if (!data.file || !data.file.stream) {
throw new BadRequest('Valid file required');
}
// Use streams to avoid holding large buffers in memory
const uploadStream = data.file.stream;
const chunks = [];
return new Promise((resolve, reject) => {
uploadStream
.on('data', chunk => chunks.push(chunk))
.on('error', reject)
.on('end', async () => {
try {
const buffer = Buffer.concat(chunks);
const processed = await this.safeProcess(buffer);
resolve(processed);
} catch (error) {
// Single cleanup point - no double free possible
reject(error);
}
});
});
}
async safeProcess(buffer) {
// Always use try/finally for cleanup
let result;
try {
result = await sharp(buffer).resize(200, 200).toBuffer();
} finally {
// Explicit cleanup - buffer will be GC'd exactly once
if (buffer) {
buffer = null;
}
}
return result;
}
}
For service composition patterns, use proper error boundaries and cleanup hooks:
const { before, after } = require('feathers-hooks-common');
class SafeOrderService {
async create(data, params) {
const context = { data, params };
try {
await this.beforeCreate(context);
const order = await this.createOrder(context.data);
// Use sequential processing with proper error handling
const payment = await this.processPayment(order);
const inventory = await this.updateInventory(order);
await this.afterCreate(order, { payment, inventory });
return { ...order, payment, inventory };
} catch (error) {
// Single error handler - prevents multiple cleanup attempts
await this.cleanupOnError(context, error);
throw error;
}
}
async cleanupOnError(context, error) {
// Centralized cleanup logic
if (context.tempFiles) {
await Promise.all(context.tempFiles.map(f => f.cleanup()));
}
}
}
middleBrick's remediation guidance includes specific Feathersjs patterns. When the scanner detects a potential double free scenario, it provides:
- Exact line numbers where cleanup is missing
- Feathersjs-specific hook patterns to implement proper resource management
- Code examples using
iffand conditional hooks to prevent race conditions
For production deployments, enable Node.js's --max-old-space-size and use async_hooks to track resource allocation:
const asyncHooks = require('async_hooks');
const resources = new Map();
const hook = asyncHooks.createHook({
init(asyncId, type, triggerAsyncId) {
if (type === 'Timeout' || type === 'Promise') {
resources.set(asyncId, { type, triggerAsyncId });
}
},
destroy(asyncId) {
resources.delete(asyncId);
}
});
hook.enable();
middleBrick's GitHub Action can be configured to fail builds if it detects these patterns in your Feathersjs codebase, ensuring double free vulnerabilities never reach production.