einvoice/test/suite/einvoice_security/test.sec-06.memory-dos.ts

479 lines
14 KiB
TypeScript
Raw Normal View History

2025-05-26 04:04:51 +00:00
import { tap } from '@git.zone/tstest/tapbundle';
import * as plugins from '../plugins.js';
import { EInvoice } from '../../../ts/index.js';
import { PerformanceTracker } from '../performance.tracker.js';
const performanceTracker = new PerformanceTracker('SEC-06: Memory DoS Prevention');
tap.test('SEC-06: Memory DoS Prevention - should prevent memory exhaustion attacks', async (t) => {
const einvoice = new EInvoice();
// Test 1: Large attribute count attack
const largeAttributeAttack = await performanceTracker.measureAsync(
'large-attribute-count-attack',
async () => {
// Create XML with excessive attributes
let attributes = '';
const attrCount = 1000000;
for (let i = 0; i < attrCount; i++) {
attributes += ` attr${i}="value${i}"`;
}
const maliciousXML = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice ${attributes}>
<ID>test</ID>
</Invoice>`;
const startMemory = process.memoryUsage();
const startTime = Date.now();
try {
await einvoice.parseXML(maliciousXML);
const endMemory = process.memoryUsage();
const endTime = Date.now();
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
const timeTaken = endTime - startTime;
return {
prevented: memoryIncrease < 100 * 1024 * 1024, // Less than 100MB
memoryIncrease,
timeTaken,
attributeCount: attrCount
};
} catch (error) {
return {
prevented: true,
rejected: true,
error: error.message
};
}
}
);
t.ok(largeAttributeAttack.prevented, 'Large attribute count attack was prevented');
// Test 2: Deep recursion attack
const deepRecursionAttack = await performanceTracker.measureAsync(
'deep-recursion-attack',
async () => {
// Create deeply nested XML
const depth = 50000;
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<Invoice>';
for (let i = 0; i < depth; i++) {
xml += `<Level${i}>`;
}
xml += 'data';
for (let i = depth - 1; i >= 0; i--) {
xml += `</Level${i}>`;
}
xml += '</Invoice>';
const startMemory = process.memoryUsage();
try {
await einvoice.parseXML(xml);
const endMemory = process.memoryUsage();
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
return {
prevented: memoryIncrease < 50 * 1024 * 1024, // Less than 50MB
memoryIncrease,
depth
};
} catch (error) {
// Stack overflow or depth limit is also prevention
return {
prevented: true,
rejected: true,
error: error.message
};
}
}
);
t.ok(deepRecursionAttack.prevented, 'Deep recursion attack was prevented');
// Test 3: Large text node attack
const largeTextNodeAttack = await performanceTracker.measureAsync(
'large-text-node-attack',
async () => {
// Create XML with huge text content
const textSize = 500 * 1024 * 1024; // 500MB of text
const chunk = 'A'.repeat(1024 * 1024); // 1MB chunks
const maliciousXML = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice>
<Description>${chunk}</Description>
</Invoice>`;
const startMemory = process.memoryUsage();
const startTime = Date.now();
try {
// Simulate streaming or chunked processing
for (let i = 0; i < 500; i++) {
await einvoice.parseXML(maliciousXML);
// Check memory growth
const currentMemory = process.memoryUsage();
const memoryGrowth = currentMemory.heapUsed - startMemory.heapUsed;
if (memoryGrowth > 200 * 1024 * 1024) {
throw new Error('Memory limit exceeded');
}
}
const endTime = Date.now();
const finalMemory = process.memoryUsage();
return {
prevented: false,
memoryGrowth: finalMemory.heapUsed - startMemory.heapUsed,
timeTaken: endTime - startTime
};
} catch (error) {
return {
prevented: true,
limited: true,
error: error.message
};
}
}
);
t.ok(largeTextNodeAttack.prevented, 'Large text node attack was prevented');
// Test 4: Namespace pollution attack
const namespacePollutionAttack = await performanceTracker.measureAsync(
'namespace-pollution-attack',
async () => {
// Create XML with excessive namespaces
let namespaces = '';
const nsCount = 100000;
for (let i = 0; i < nsCount; i++) {
namespaces += ` xmlns:ns${i}="http://example.com/ns${i}"`;
}
const maliciousXML = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice${namespaces}>
<ID>test</ID>
</Invoice>`;
const startMemory = process.memoryUsage();
try {
await einvoice.parseXML(maliciousXML);
const endMemory = process.memoryUsage();
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
return {
prevented: memoryIncrease < 50 * 1024 * 1024,
memoryIncrease,
namespaceCount: nsCount
};
} catch (error) {
return {
prevented: true,
rejected: true
};
}
}
);
t.ok(namespacePollutionAttack.prevented, 'Namespace pollution attack was prevented');
// Test 5: Entity expansion memory attack
const entityExpansionMemory = await performanceTracker.measureAsync(
'entity-expansion-memory-attack',
async () => {
// Create entities that expand exponentially
const maliciousXML = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY base "AAAAAAAAAA">
<!ENTITY level1 "&base;&base;&base;&base;&base;&base;&base;&base;&base;&base;">
<!ENTITY level2 "&level1;&level1;&level1;&level1;&level1;&level1;&level1;&level1;&level1;&level1;">
<!ENTITY level3 "&level2;&level2;&level2;&level2;&level2;&level2;&level2;&level2;&level2;&level2;">
]>
<Invoice>
<Data>&level3;</Data>
</Invoice>`;
const startMemory = process.memoryUsage();
const memoryLimit = 100 * 1024 * 1024; // 100MB limit
try {
await einvoice.parseXML(maliciousXML);
const endMemory = process.memoryUsage();
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
return {
prevented: memoryIncrease < memoryLimit,
memoryIncrease,
expansionFactor: Math.pow(10, 3) // Expected expansion
};
} catch (error) {
return {
prevented: true,
rejected: true,
error: error.message
};
}
}
);
t.ok(entityExpansionMemory.prevented, 'Entity expansion memory attack was prevented');
// Test 6: Array allocation attack
const arrayAllocationAttack = await performanceTracker.measureAsync(
'array-allocation-attack',
async () => {
// Create XML that forces large array allocations
let elements = '';
const elementCount = 10000000;
for (let i = 0; i < elementCount; i++) {
elements += `<Item${i}/>`;
}
const maliciousXML = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice>
<Items>${elements}</Items>
</Invoice>`;
const startMemory = process.memoryUsage();
try {
await einvoice.parseXML(maliciousXML);
const endMemory = process.memoryUsage();
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
return {
prevented: memoryIncrease < 200 * 1024 * 1024,
memoryIncrease,
elementCount
};
} catch (error) {
return {
prevented: true,
rejected: true
};
}
}
);
t.ok(arrayAllocationAttack.prevented, 'Array allocation attack was prevented');
// Test 7: Memory leak through repeated operations
const memoryLeakTest = await performanceTracker.measureAsync(
'memory-leak-prevention',
async () => {
const iterations = 1000;
const samples = [];
// Force GC if available
if (global.gc) {
global.gc();
}
const baselineMemory = process.memoryUsage().heapUsed;
for (let i = 0; i < iterations; i++) {
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice>
<ID>INV-${i}</ID>
<Amount>${Math.random() * 1000}</Amount>
</Invoice>`;
await einvoice.parseXML(xml);
if (i % 100 === 0) {
// Sample memory every 100 iterations
const currentMemory = process.memoryUsage().heapUsed;
samples.push({
iteration: i,
memory: currentMemory - baselineMemory
});
}
}
// Calculate memory growth trend
const firstSample = samples[0];
const lastSample = samples[samples.length - 1];
const memoryGrowthRate = (lastSample.memory - firstSample.memory) / (lastSample.iteration - firstSample.iteration);
return {
prevented: memoryGrowthRate < 1000, // Less than 1KB per iteration
memoryGrowthRate,
totalIterations: iterations,
samples
};
}
);
t.ok(memoryLeakTest.prevented, 'Memory leak through repeated operations was prevented');
// Test 8: Concurrent memory attacks
const concurrentMemoryAttack = await performanceTracker.measureAsync(
'concurrent-memory-attacks',
async () => {
const concurrentAttacks = 10;
const startMemory = process.memoryUsage();
// Create multiple large XML documents
const createLargeXML = (id: number) => {
const size = 10 * 1024 * 1024; // 10MB
const data = 'X'.repeat(size);
return `<?xml version="1.0" encoding="UTF-8"?>
<Invoice>
<ID>${id}</ID>
<LargeData>${data}</LargeData>
</Invoice>`;
};
try {
// Process multiple large documents concurrently
const promises = [];
for (let i = 0; i < concurrentAttacks; i++) {
promises.push(einvoice.parseXML(createLargeXML(i)));
}
await Promise.all(promises);
const endMemory = process.memoryUsage();
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
return {
prevented: memoryIncrease < 500 * 1024 * 1024, // Less than 500MB total
memoryIncrease,
concurrentCount: concurrentAttacks
};
} catch (error) {
return {
prevented: true,
rejected: true,
error: error.message
};
}
}
);
t.ok(concurrentMemoryAttack.prevented, 'Concurrent memory attacks were prevented');
// Test 9: Cache pollution attack
const cachePollutionAttack = await performanceTracker.measureAsync(
'cache-pollution-attack',
async () => {
const uniqueDocuments = 10000;
const startMemory = process.memoryUsage();
try {
// Parse many unique documents to pollute cache
for (let i = 0; i < uniqueDocuments; i++) {
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice>
<UniqueID>ID-${Math.random()}-${Date.now()}-${i}</UniqueID>
<RandomData>${Math.random().toString(36).substring(2)}</RandomData>
</Invoice>`;
await einvoice.parseXML(xml);
// Check memory growth periodically
if (i % 1000 === 0) {
const currentMemory = process.memoryUsage();
const memoryGrowth = currentMemory.heapUsed - startMemory.heapUsed;
if (memoryGrowth > 100 * 1024 * 1024) {
throw new Error('Cache memory limit exceeded');
}
}
}
const endMemory = process.memoryUsage();
const totalMemoryGrowth = endMemory.heapUsed - startMemory.heapUsed;
return {
prevented: totalMemoryGrowth < 100 * 1024 * 1024,
memoryGrowth: totalMemoryGrowth,
documentsProcessed: uniqueDocuments
};
} catch (error) {
return {
prevented: true,
limited: true,
error: error.message
};
}
}
);
t.ok(cachePollutionAttack.prevented, 'Cache pollution attack was prevented');
// Test 10: Memory exhaustion recovery
const memoryExhaustionRecovery = await performanceTracker.measureAsync(
'memory-exhaustion-recovery',
async () => {
const results = {
attacksAttempted: 0,
attacksPrevented: 0,
recovered: false
};
// Try various memory attacks
const attacks = [
() => 'A'.repeat(100 * 1024 * 1024), // 100MB string
() => new Array(10000000).fill('data'), // Large array
() => { const obj = {}; for(let i = 0; i < 1000000; i++) obj[`key${i}`] = i; return obj; } // Large object
];
for (const attack of attacks) {
results.attacksAttempted++;
try {
const payload = attack();
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice>
<Data>${JSON.stringify(payload).substring(0, 1000)}</Data>
</Invoice>`;
await einvoice.parseXML(xml);
} catch (error) {
results.attacksPrevented++;
}
}
// Test if system recovered and can process normal documents
try {
const normalXML = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice>
<ID>NORMAL-001</ID>
<Amount>100.00</Amount>
</Invoice>`;
await einvoice.parseXML(normalXML);
results.recovered = true;
} catch (error) {
results.recovered = false;
}
return results;
}
);
t.equal(memoryExhaustionRecovery.attacksPrevented, memoryExhaustionRecovery.attacksAttempted, 'All memory attacks were prevented');
t.ok(memoryExhaustionRecovery.recovered, 'System recovered after memory attacks');
// Print performance summary
performanceTracker.printSummary();
});
// Run the test
tap.start();