Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: modernize fuzzing #3060

Merged
merged 1 commit into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -74,6 +74,7 @@
"test:node-fetch": "borp -p \"test/node-fetch/**/*.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\" && borp -p \"test/webidl/*.js\" && borp -p \"test/busboy/*.js\"",
"test:jest": "cross-env NODE_V8_COVERAGE= jest",
Expand All @@ -92,8 +93,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 @@ -104,13 +104,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
Loading