Skip to content

Introduce Fuzzing with jsFuzz and fix issues found by fuzzing #176

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

Merged
merged 6 commits into from
May 4, 2021
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
23 changes: 23 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/jsfuzz

name: Fuzz

on:
push:
branches:
- main
pull_request:

jobs:
fuzzing:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: "16"

- run: npm ci
- run: npm run test:fuzz
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ isolate-*.log

# flamebearer
flamegraph.html

# jsfuzz
corpus/
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"test:cover:purejs": "npx nyc --no-clean npm run test:purejs",
"test:cover:te": "npx nyc --no-clean npm run test:te",
"test:deno": "deno test test/deno_test.ts",
"test:fuzz": "npm exec -- jsfuzz@git+https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/jsfuzz.git --fuzzTime 60 --no-versifier test/decode.jsfuzz.js corpus",
"cover:clean": "rimraf .nyc_output coverage/",
"cover:report": "npx nyc report --reporter=text-summary --reporter=html --reporter=json",
"test:browser": "karma start --single-run",
Expand Down
16 changes: 16 additions & 0 deletions src/DecodeError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

export class DecodeError extends Error {
constructor(message: string) {
super(message);

// fix the prototype chain in a cross-platform way
const proto: typeof DecodeError.prototype = Object.create(DecodeError.prototype);
Object.setPrototypeOf(this, proto);

Object.defineProperty(this, "name", {
configurable: true,
enumerable: false,
value: DecodeError.name,
});
}
}
21 changes: 5 additions & 16 deletions src/Decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getInt64, getUint64 } from "./utils/int";
import { utf8DecodeJs, TEXT_DECODER_THRESHOLD, utf8DecodeTD } from "./utils/utf8";
import { createDataView, ensureUint8Array } from "./utils/typedArrays";
import { CachedKeyDecoder, KeyDecoder } from "./CachedKeyDecoder";
import { DecodeError } from "./DecodeError";

const enum State {
ARRAY,
Expand Down Expand Up @@ -60,22 +61,6 @@ const DEFAULT_MAX_LENGTH = 0xffff_ffff; // uint32_max

const sharedCachedKeyDecoder = new CachedKeyDecoder();

export class DecodeError extends Error {
constructor(message: string) {
super(message);

// fix the prototype chain in a cross-platform way
const proto: typeof DecodeError.prototype = Object.create(DecodeError.prototype);
Object.setPrototypeOf(this, proto);

Object.defineProperty(this, "name", {
configurable: true,
enumerable: false,
value: DecodeError.name,
});
}
}

export class Decoder<ContextType = undefined> {
private totalPos = 0;
private pos = 0;
Expand Down Expand Up @@ -133,6 +118,10 @@ export class Decoder<ContextType = undefined> {
return new RangeError(`Extra ${view.byteLength - pos} of ${view.byteLength} byte(s) found at buffer[${posToShow}]`);
}

/**
* @throws {DecodeError}
* @throws {RangeError}
*/
public decode(buffer: ArrayLike<number> | BufferSource): unknown {
this.reinitializeState();
this.setBuffer(buffer);
Expand Down
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ export { DecodeOptions };
import { decodeAsync, decodeArrayStream, decodeMultiStream, decodeStream } from "./decodeAsync";
export { decodeAsync, decodeArrayStream, decodeMultiStream, decodeStream };

import { Decoder, DecodeError } from "./Decoder";
export { Decoder, DecodeError };
import { Decoder, DataViewIndexOutOfBoundsError } from "./Decoder";
import { DecodeError } from "./DecodeError";
export { Decoder, DecodeError, DataViewIndexOutOfBoundsError };

import { Encoder } from "./Encoder";
export { Encoder };
Expand Down
3 changes: 2 additions & 1 deletion src/timestamp.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
import { DecodeError } from "./DecodeError";
import { getInt64, setInt64 } from "./utils/int";

export const EXT_TIMESTAMP = -1;
Expand Down Expand Up @@ -91,7 +92,7 @@ export function decodeTimestampToTimeSpec(data: Uint8Array): TimeSpec {
return { sec, nsec };
}
default:
throw new Error(`Unrecognized data size for timestamp: ${data.length}`);
throw new DecodeError(`Unrecognized data size for timestamp (expected 4, 8, or 12): ${data.length}`);
}
}

Expand Down
30 changes: 30 additions & 0 deletions test/decode.jsfuzz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint-disable */
const assert = require("assert");
const { Decoder, encode, DecodeError } = require("../dist/index.js");

/**
* @param {Buffer} bytes
* @returns {void}
*/
module.exports.fuzz = function fuzz(bytes) {
const decoder = new Decoder();
try {
decoder.decode(bytes);
} catch (e) {
if (e instanceof DecodeError) {
// ok
} else if (e instanceof RangeError) {
// ok
} else {
throw e;
}
}

// make sure the decoder instance is not broken
const object = {
foo: 1,
bar: 2,
baz: ["one", "two", "three"],
};
assert.deepStrictEqual(decoder.decode(encode(object)), object);
}