Skip to content

Commit

Permalink
feat: modernize fuzzing (#3060)
Browse files Browse the repository at this point in the history
  • Loading branch information
Uzlopak committed Apr 7, 2024
1 parent e5ae400 commit 1a638d2
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 123 deletions.
39 changes: 0 additions & 39 deletions .github/workflows/fuzz.yml

This file was deleted.

21 changes: 21 additions & 0 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,26 @@ jobs:
- name: Run tests
run: npm run test:javascript:withoutintl

test-fuzzing:
name: Fuzzing
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: lts/*

- name: Install dependencies
run: npm install

- name: Run fuzzing tests
run: npm run test:fuzzing

test-types:
name: Test TypeScript types
timeout-minutes: 15
Expand Down Expand Up @@ -169,6 +189,7 @@ jobs:
- test
- test-types
- test-without-intl
- test-fuzzing
- lint
runs-on: ubuntu-latest
permissions:
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"test:cookies": "borp -p \"test/cookie/*.js\"",
"test:eventsource": "npm run build:node && npm run test:eventsource:nobuild",
"test:eventsource:nobuild": "borp --expose-gc -p \"test/eventsource/*.js\"",
"test:fuzzing": "node test/fuzzing/fuzzing.test.js",
"test:fetch": "npm run build:node && npm run test:fetch:nobuild",
"test:fetch:nobuild": "borp --expose-gc -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy",
"test:interceptors": "borp -p \"test/interceptors/*.js\"",
Expand All @@ -96,8 +97,7 @@
"coverage:report:ci": "c8 report",
"bench": "echo \"Error: Benchmarks have been moved to '/benchmarks'\" && exit 1",
"serve:website": "echo \"Error: Documentation has been moved to '/docs'\" && exit 1",
"prepare": "husky install && node ./scripts/platform-shell.js",
"fuzz": "jsfuzz test/fuzzing/fuzz.js corpus"
"prepare": "husky install && node ./scripts/platform-shell.js"
},
"devDependencies": {
"@matteo.collina/tspl": "^0.1.1",
Expand All @@ -108,13 +108,13 @@
"c8": "^9.1.0",
"cross-env": "^7.0.3",
"dns-packet": "^5.4.0",
"fast-check": "^3.17.1",
"form-data": "^4.0.0",
"formdata-node": "^6.0.3",
"https-pem": "^3.0.0",
"husky": "^9.0.7",
"jest": "^29.0.2",
"jsdom": "^24.0.0",
"jsfuzz": "^1.0.15",
"node-forge": "^1.3.1",
"pre-commit": "^1.2.2",
"proxy": "^2.1.1",
Expand Down
6 changes: 2 additions & 4 deletions test/fuzzing/client/client-fuzz-body.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@ 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) {
async function fuzz (address, results, buf) {
const body = buf
results.body = body
try {
const data = await request(`http://localhost:${netServer.address().port}`, { body })
const data = await request(address, { 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
}
}
Expand Down
5 changes: 2 additions & 3 deletions test/fuzzing/client/client-fuzz-headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,18 @@ const acceptableCodes = [
'ERR_INVALID_ARG_TYPE'
]

async function fuzz (netServer, results, buf) {
async function fuzz (address, results, buf) {
const headers = { buf: buf.toString() }
results.body = headers
try {
const data = await request(`http://localhost:${netServer.address().port}`, { headers })
const data = await request(address, { 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
}
}
Expand Down
6 changes: 2 additions & 4 deletions test/fuzzing/client/client-fuzz-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ const acceptableCodes = [
'ENOTFOUND',
'EAI_AGAIN',
'ECONNREFUSED'
// ----
]

async function fuzz (netServer, results, buf) {
async function fuzz (address, results, buf) {
const optionKeys = ['body', 'path', 'method', 'opaque', 'upgrade', buf]
const options = {}
for (const optionKey of optionKeys) {
Expand All @@ -21,15 +20,14 @@ async function fuzz (netServer, results, buf) {
}
results.options = options
try {
const data = await request(`http://localhost:${netServer.address().port}`, options)
const data = await request(address, 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
}
}
Expand Down
66 changes: 0 additions & 66 deletions test/fuzzing/fuzz.js

This file was deleted.

64 changes: 64 additions & 0 deletions test/fuzzing/fuzzing.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use strict'

const { once } = require('node:events')
const fc = require('fast-check')
const netServer = require('./server')
const { describe, before, after, test } = require('node:test')
const {
clientFuzzBody,
clientFuzzHeaders,
clientFuzzOptions
} = require('./client')

// Detect if running in CI (here we use GitHub Workflows)
// https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
const isCI = process.env.CI === 'true'

fc.configureGlobal({
interruptAfterTimeLimit: isCI ? 60_000 /* 1 minute */ : 10_000 /* 10 seconds */,
numRuns: Number.MAX_SAFE_INTEGER
})

describe('fuzzing', { timeout: 600_000 /* 10 minutes */ }, () => {
before(async () => {
netServer.listen(0)
await once(netServer, 'listening')
})

after(() => {
netServer.close()
})

test('body', async () => {
const address = `http://localhost:${netServer.address().port}`
await fc.assert(
fc.asyncProperty(fc.uint8Array(), async (body) => {
body = Buffer.from(body)
const results = {}
await clientFuzzBody(address, results, body)
})
)
})

test('headers', async () => {
const address = `http://localhost:${netServer.address().port}`
await fc.assert(
fc.asyncProperty(fc.uint8Array(), async (body) => {
body = Buffer.from(body)
const results = {}
await clientFuzzHeaders(address, results, body)
})
)
})

test('options', async () => {
const address = `http://localhost:${netServer.address().port}`
await fc.assert(
fc.asyncProperty(fc.uint8Array(), async (body) => {
body = Buffer.from(body)
const results = {}
await clientFuzzOptions(address, results, body)
})
)
})
})
17 changes: 13 additions & 4 deletions test/fuzzing/server/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
'use strict'

module.exports = {
splitData: require('./server-fuzz-split-data'),
appendData: require('./server-fuzz-append-data')
}
const net = require('node:net')
const serverFuzzFns = [
require('./server-fuzz-append-data'),
require('./server-fuzz-split-data')
]

const netServer = net.createServer(socket => {
socket.on('data', data => {
serverFuzzFns[(Math.random() * 2) | 0](socket, data)
})
})

module.exports = netServer

0 comments on commit 1a638d2

Please sign in to comment.