summaryrefslogtreecommitdiffstats
path: root/test/fuzzing
diff options
context:
space:
mode:
Diffstat (limited to 'test/fuzzing')
-rw-r--r--test/fuzzing/client/client-fuzz-body.js28
-rw-r--r--test/fuzzing/client/client-fuzz-headers.js27
-rw-r--r--test/fuzzing/client/client-fuzz-options.js38
-rw-r--r--test/fuzzing/client/index.js7
-rw-r--r--test/fuzzing/fuzz.js66
-rw-r--r--test/fuzzing/server/index.js6
-rw-r--r--test/fuzzing/server/server-fuzz-append-data.js7
-rw-r--r--test/fuzzing/server/server-fuzz-split-data.js17
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