Stack Overflow in Firestore
How Stack Overflow Manifests in Firestore
Stack Overflow vulnerabilities in Firestore occur when applications fail to validate the depth or size of nested data structures being read or written. Unlike traditional SQL databases, Firestore's flexible document model allows arbitrarily nested objects, making it particularly susceptible to recursive attacks that can exhaust memory or processing resources.
The most common Firestore Stack Overflow pattern emerges when applications recursively traverse user-controlled document hierarchies. Consider a document structure where users can create nested folders:
// Vulnerable recursive traversal
function getAllNestedFolders(folderId) {
const folder = db.collection('folders').doc(folderId).get();
const subfolders = folder.data().subfolders || [];
subfolders.forEach(subfolder => {
getAllNestedFolders(subfolder.id); // No depth limit!
});
}
An attacker can create a folder structure with thousands of nested levels, causing the recursive function to exceed the JavaScript call stack limit (typically around 10,000-15,000 calls). This results in a RangeError: Maximum call stack size exceeded error, potentially crashing the application or creating a denial of service condition.
Another variant involves Firestore's array-contains queries on deeply nested arrays. When applications process query results without size limits:
// Vulnerable array processing
async function processUserTags(userId) {
const userDoc = await db.collection('users').doc(userId).get();
const tags = userDoc.data().tags || [];
// Process without limits
tags.forEach(tag => {
await processTag(tag); // Can be called thousands of times
});
}
Attackers can populate the tags array with millions of entries, causing the forEach loop to consume excessive memory and processing time. Firestore itself allows arrays up to 20,000 elements, but the application layer may crash long before reaching that limit.
Firestore's array-contains-any queries present another attack vector. When applications build queries based on user input without validating array sizes:
// Vulnerable query construction
async function searchProducts(tags) {
return await db.collection('products')
.where('tags', 'array-contains-any', tags)
.get();
}
If tags contains thousands of elements, Firestore must scan a massive number of documents, potentially causing timeouts or excessive billing charges. The query becomes a vector for both Stack Overflow (through processing) and resource exhaustion.
Recursive document imports also create Stack Overflow risks. Applications that recursively import document structures without depth limits:
// Vulnerable recursive import
async function importDocument(docData, depth = 0) {
if (depth > 100) return; // Missing validation!
const docRef = await db.collection('documents').add(docData);
const children = docData.children || [];
for (const child of children) {
await importDocument(child, depth + 1);
}
}
Without proper depth validation, attackers can create documents with recursive structures that cause the import process to fail catastrophically.
Firestore-Specific Detection
Detecting Stack Overflow vulnerabilities in Firestore requires both static code analysis and runtime monitoring. Static analysis tools should scan for recursive functions that interact with Firestore collections without depth or size limits.
middleBrick's Firestore-specific scanning identifies these patterns through black-box testing. The scanner creates test documents with extreme nesting levels and monitors application responses. For recursive traversal vulnerabilities, middleBrick generates documents with 1,000+ nested levels and observes whether the application handles them gracefully or crashes.
Key detection patterns include:
- Recursive functions calling themselves with Firestore document data as parameters
- Array processing without size validation (forEach, map, reduce on user-controlled arrays)
- Query construction using user input without array length validation
- Document import/export functions without depth limits
- Event handlers processing Firestore document snapshots without error boundaries
middleBrick specifically tests for Firestore's array-contains and array-contains-any query vulnerabilities by creating documents with oversized arrays and measuring response times and error rates. The scanner also tests recursive document structures to identify stack overflow conditions.
Runtime monitoring should track:
// Stack overflow monitoring
function monitorStackUsage() {
const stackTrace = new Error().stack;
const stackDepth = stackTrace.split('\n').length;
if (stackDepth > 500) {
console.warn('High stack depth detected:', stackDepth);
// Alert or log for analysis
}
}
Cloud Functions and Firebase triggers require special attention. When a Firestore document triggers a Cloud Function, that function must validate document structure before processing:
exports.onDocumentCreate = functions.firestore
.document('users/{userId}')
.onCreate(async (snap, context) => {
const data = snap.data();
// Stack overflow prevention
if (data.nested && typeof data.nested === 'object') {
const nestedKeys = Object.keys(data.nested);
if (nestedKeys.length > 100) {
throw new functions.https.HttpsError(
'invalid-argument',
'Nested data too deep'
);
}
}
});
middleBrick's scanning also identifies improper error handling that could mask stack overflow conditions. Applications that catch all errors without proper logging may silently fail when stack overflows occur, leaving security vulnerabilities undetected.
Firestore-Specific Remediation
Firestore-specific Stack Overflow remediation requires implementing depth limits, size validation, and proper error handling. The most effective approach combines defensive coding with Firestore's built-in validation features.
For recursive functions, implement maximum depth limits and iterative alternatives:
// Safe recursive traversal with depth limit
const MAX_DEPTH = 50;
async function getNestedFolders(folderId, depth = 0) {
if (depth > MAX_DEPTH) {
throw new Error('Maximum nesting depth exceeded');
}
const folder = await db.collection('folders').doc(folderId).get();
const subfolders = folder.data().subfolders || [];
const results = [folder.data()];
for (const subfolder of subfolders) {
const nested = await getNestedFolders(subfolder.id, depth + 1);
results.push(...nested);
}
return results;
}
Better yet, use iterative approaches with explicit stacks to avoid recursion entirely:
// Iterative folder traversal
async function getAllFoldersIterative(rootFolderId) {
const stack = [rootFolderId];
const visited = new Set();
const results = [];
while (stack.length > 0) {
const folderId = stack.pop();
if (visited.has(folderId)) continue;
visited.add(folderId);
const folder = await db.collection('folders').doc(folderId).get();
results.push(folder.data());
const subfolders = folder.data().subfolders || [];
stack.push(...subfolders.map(f => f.id));
}
return results;
}
For array processing, implement size limits and pagination:
// Safe array processing
async function processUserTags(userId) {
const userDoc = await db.collection('users').doc(userId).get();
const tags = userDoc.data().tags || [];
if (tags.length > 1000) {
throw new Error('Too many tags to process');
}
// Process in batches to avoid memory issues
const BATCH_SIZE = 100;
for (let i = 0; i < tags.length; i += BATCH_SIZE) {
const batch = tags.slice(i, i + BATCH_SIZE);
await Promise.all(batch.map(tag => processTag(tag)));
}
}
Firestore security rules can provide an additional layer of protection by limiting document structure complexity:
For query validation, implement strict input sanitization:
// Safe query construction
async function searchProductsWithValidation(tags) {
if (!Array.isArray(tags)) {
throw new Error('Tags must be an array');
}
if (tags.length > 100) {
throw new Error('Too many tags in query');
}
// Validate tag format
for (const tag of tags) {
if (typeof tag !== 'string' || tag.length > 50) {
throw new Error('Invalid tag format');
}
}
return await db.collection('products')
.where('tags', 'array-contains-any', tags)
.get();
}
Implement comprehensive error handling with stack overflow detection:
// Stack overflow error handling
process.on('uncaughtException', (err) => {
if (err.message.includes('Maximum call stack size exceeded')) {
console.error('Stack overflow detected:', err);
// Alert security team
alertSecurityTeam('Stack overflow attempt detected');
} else {
console.error('Uncaught exception:', err);
}
});
middleBrick's remediation guidance includes specific Firestore security rule templates that prevent malicious document structures from being written in the first place, blocking attacks before they can cause stack overflows.