diff options
Diffstat (limited to 'test/fuzzing')
-rw-r--r-- | test/fuzzing/client/client-fuzz-body.js | 28 | ||||
-rw-r--r-- | test/fuzzing/client/client-fuzz-headers.js | 27 | ||||
-rw-r--r-- | test/fuzzing/client/client-fuzz-options.js | 38 | ||||
-rw-r--r-- | test/fuzzing/client/index.js | 7 | ||||
-rw-r--r-- | test/fuzzing/fuzz.js | 66 | ||||
-rw-r--r-- | test/fuzzing/server/index.js | 6 | ||||
-rw-r--r-- | test/fuzzing/server/server-fuzz-append-data.js | 7 | ||||
-rw-r--r-- | test/fuzzing/server/server-fuzz-split-data.js | 17 |
8 files changed, 196 insertions, 0 deletions
diff --git a/test/fuzzing/client/client-fuzz-body.js b/test/fuzzing/client/client-fuzz-body.js new file mode 100644 index 0000000..6643dda --- /dev/null +++ b/test/fuzzing/client/client-fuzz-body.js @@ -0,0 +1,28 @@ +'use strict' + +const { request, errors } = require('../../..') + +const acceptableCodes = [ + 'ERR_INVALID_ARG_TYPE' +] + +// TODO: could make this a class with some inbuilt functionality that we can inherit +async function fuzz (netServer, results, buf) { + const body = buf + results.body = body + try { + const data = await request(`http://localhost:${netServer.address().port}`, { body }) + data.body.destroy().on('error', () => {}) + } catch (err) { + results.err = err + // Handle any undici errors + if (Object.values(errors).some(undiciError => err instanceof undiciError)) { + // Okay error + } else if (!acceptableCodes.includes(err.code)) { + console.log(`=== Headers: ${JSON.stringify(body)} ===`) + throw err + } + } +} + +module.exports = fuzz diff --git a/test/fuzzing/client/client-fuzz-headers.js b/test/fuzzing/client/client-fuzz-headers.js new file mode 100644 index 0000000..84f3390 --- /dev/null +++ b/test/fuzzing/client/client-fuzz-headers.js @@ -0,0 +1,27 @@ +'use strict' + +const { request, errors } = require('../../..') + +const acceptableCodes = [ + 'ERR_INVALID_ARG_TYPE' +] + +async function fuzz (netServer, results, buf) { + const headers = { buf: buf.toString() } + results.body = headers + try { + const data = await request(`http://localhost:${netServer.address().port}`, { headers }) + data.body.destroy().on('error', () => {}) + } catch (err) { + results.err = err + // Handle any undici errors + if (Object.values(errors).some(undiciError => err instanceof undiciError)) { + // Okay error + } else if (!acceptableCodes.includes(err.code)) { + console.log(`=== Headers: ${JSON.stringify(headers)} ===`) + throw err + } + } +} + +module.exports = fuzz diff --git a/test/fuzzing/client/client-fuzz-options.js b/test/fuzzing/client/client-fuzz-options.js new file mode 100644 index 0000000..5be81b6 --- /dev/null +++ b/test/fuzzing/client/client-fuzz-options.js @@ -0,0 +1,38 @@ +'use strict' + +const { request, errors } = require('../../..') + +const acceptableCodes = [ + 'ERR_INVALID_URL', + // These are included because '\\ABC' is interpreted as a Windows UNC path and can cause these errors. + 'ENOTFOUND', + 'EAI_AGAIN', + 'ECONNREFUSED' + // ---- +] + +async function fuzz (netServer, results, buf) { + const optionKeys = ['body', 'path', 'method', 'opaque', 'upgrade', buf] + const options = {} + for (const optionKey of optionKeys) { + if (Math.random() < 0.5) { + options[optionKey] = buf.toString() + } + } + results.options = options + try { + const data = await request(`http://localhost:${netServer.address().port}`, options) + data.body.destroy().on('error', () => {}) + } catch (err) { + results.err = err + // Handle any undici errors + if (Object.values(errors).some(undiciError => err instanceof undiciError)) { + // Okay error + } else if (!acceptableCodes.includes(err.code)) { + console.log(`=== Options: ${JSON.stringify(options)} ===`) + throw err + } + } +} + +module.exports = fuzz diff --git a/test/fuzzing/client/index.js b/test/fuzzing/client/index.js new file mode 100644 index 0000000..dac3d98 --- /dev/null +++ b/test/fuzzing/client/index.js @@ -0,0 +1,7 @@ +'use strict' + +module.exports = { + clientFuzzBody: require('./client-fuzz-body'), + clientFuzzHeaders: require('./client-fuzz-headers'), + clientFuzzOptions: require('./client-fuzz-options') +} diff --git a/test/fuzzing/fuzz.js b/test/fuzzing/fuzz.js new file mode 100644 index 0000000..c268178 --- /dev/null +++ b/test/fuzzing/fuzz.js @@ -0,0 +1,66 @@ +'use strict' + +const net = require('net') +const fs = require('fs/promises') +const path = require('path') +const serverFuzzFnMap = require('./server') +const clientFuzzFnMap = require('./client') + +const port = process.env.PORT || 0 +const timeout = parseInt(process.env.TIMEOUT, 10) || 300_000 // 5 minutes by default + +const netServer = net.createServer((socket) => { + socket.on('data', (data) => { + // Select server fuzz fn + const serverFuzzFns = Object.values(serverFuzzFnMap) + const serverFuzzFn = serverFuzzFns[Math.floor(Math.random() * serverFuzzFns.length)] + + serverFuzzFn(socket, data) + }) +}) +const waitForNetServer = netServer.listen(port) + +// Set script to exit gracefully after a set period of time. +const timer = setTimeout(() => { + process.kill(process.pid, 'SIGINT') +}, timeout) + +async function writeResults (resultsPath, data) { + try { + await fs.writeFile(resultsPath, JSON.stringify(data, null, 2)) + console.log(`=== Written results to ${resultsPath} ===`) + } catch (err) { + console.log(`=== Unable to write results to ${resultsPath}`, err, '===') + } +} + +async function fuzz (buf) { + // Wait for net server to be ready + await waitForNetServer + + // Select client fuzz fn based on the buf input + await Promise.all( + Object.entries(clientFuzzFnMap).map(async ([clientFuzzFnName, clientFuzzFn]) => { + const results = {} + try { + await clientFuzzFn(netServer, results, buf) + } catch (err) { + clearTimeout(timer) + const output = { clientFuzzFnName, buf: { raw: buf, string: buf.toString() }, raw: JSON.stringify({ clientFuzzFnName, buf: { raw: buf, string: buf.toString() }, err, ...results }), err, ...results } + + console.log(`=== Failed fuzz ${clientFuzzFnName} with input '${buf}' ===`) + console.log('=== Fuzz results start ===') + console.log(output) + console.log('=== Fuzz results end ===') + + await writeResults(path.resolve(`fuzz-results-${Date.now()}.json`), output) + + throw err + } + }) + ) +} + +module.exports = { + fuzz +} diff --git a/test/fuzzing/server/index.js b/test/fuzzing/server/index.js new file mode 100644 index 0000000..4bef554 --- /dev/null +++ b/test/fuzzing/server/index.js @@ -0,0 +1,6 @@ +'use strict' + +module.exports = { + splitData: require('./server-fuzz-split-data'), + appendData: require('./server-fuzz-append-data') +} diff --git a/test/fuzzing/server/server-fuzz-append-data.js b/test/fuzzing/server/server-fuzz-append-data.js new file mode 100644 index 0000000..8ef6c45 --- /dev/null +++ b/test/fuzzing/server/server-fuzz-append-data.js @@ -0,0 +1,7 @@ +'use strict' + +function appendData (socket, data) { + socket.end('HTTP/1.1 200 OK' + data) +} + +module.exports = appendData diff --git a/test/fuzzing/server/server-fuzz-split-data.js b/test/fuzzing/server/server-fuzz-split-data.js new file mode 100644 index 0000000..5e057dc --- /dev/null +++ b/test/fuzzing/server/server-fuzz-split-data.js @@ -0,0 +1,17 @@ +'use strict' + +function splitData (socket, data) { + const lines = [ + 'HTTP/1.1 200 OK', + 'Date: Sat, 09 Oct 2010 14:28:02 GMT', + 'Connection: close', + '', + data + ] + for (const line of lines.join('\r\n').split(data)) { + socket.write(line) + } + socket.end() +} + +module.exports = splitData |