From 2de5565ed86af8c5db9b8144a49fe7af05c3f435 Mon Sep 17 00:00:00 2001 From: Xander Bil Date: Wed, 9 Oct 2024 19:23:49 +0200 Subject: [PATCH] add fuzzer --- Cargo.lock | 53 +++++++++++++++++++++++- Cargo.toml | 1 + fuzz/.gitignore | 4 ++ fuzz/Cargo.toml | 19 +++++++++ fuzz/cov.sh | 42 +++++++++++++++++++ fuzz/flake.lock | 82 +++++++++++++++++++++++++++++++++++++ fuzz/flake.nix | 31 ++++++++++++++ fuzz/fuzz_targets/parser.rs | 9 ++++ zns/Cargo.toml | 1 + zns/src/labelstring.rs | 1 + zns/src/reader.rs | 6 +-- zns/src/structs.rs | 8 ++++ 12 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.toml create mode 100755 fuzz/cov.sh create mode 100644 fuzz/flake.lock create mode 100644 fuzz/flake.nix create mode 100644 fuzz/fuzz_targets/parser.rs diff --git a/Cargo.lock b/Cargo.lock index 183b7d7..84f721f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -66,6 +66,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "asn1" version = "0.16.2" @@ -149,6 +158,8 @@ version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -255,6 +266,17 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "diesel" version = "2.2.2" @@ -640,6 +662,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.70" @@ -655,6 +686,17 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1614,6 +1656,7 @@ checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" name = "zns" version = "0.1.0" dependencies = [ + "arbitrary", "base64", "int-enum", "thiserror", @@ -1645,3 +1688,11 @@ dependencies = [ "tokio", "zns", ] + +[[package]] +name = "zns-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "zns", +] diff --git a/Cargo.toml b/Cargo.toml index 19d7240..28babcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ members = [ "zns", "zns-cli", "zns-daemon", + "fuzz" ] diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..1a45eee --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..0c67660 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "zns-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +zns = {path = "../zns", features = ["arbitrary"]} + +[[bin]] +name = "parser" +path = "fuzz_targets/parser.rs" +test = false +doc = false +bench = false diff --git a/fuzz/cov.sh b/fuzz/cov.sh new file mode 100755 index 0000000..799e582 --- /dev/null +++ b/fuzz/cov.sh @@ -0,0 +1,42 @@ +#!/bin/env bash +set -e + +# Check if the correct number of arguments is provided +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Assign the first argument to the fuzz_target variable +COMMAND=$1 +FUZZ_TARGET=$2 + + +if ! command -v $(COMMAND) &> /dev/null; then + echo "llvm-cov could not be found, please install LLVM." + exit 1 +fi + +if ! command -v rustfilt &> /dev/null; then + echo "rustfilt could not be found, please install rustfilt." + exit 1 +fi + +cargo fuzz coverage "$FUZZ_TARGET" + +TARGET_DIR="target/x86_64-unknown-linux-gnu/coverage/x86_64-unknown-linux-gnu/release" +PROF_DATA="coverage/$FUZZ_TARGET/coverage.profdata" +OUTPUT_FILE="coverage/index.html" + +if [ ! -f "$PROF_DATA" ]; then + echo "Coverage data file $PROF_DATA not found." + exit 1 +fi + +$COMMAND show "$TARGET_DIR/$FUZZ_TARGET" --format=html \ + -Xdemangler=rustfilt \ + --ignore-filename-regex="\.cargo" \ + -instr-profile="$PROF_DATA" \ + > "$OUTPUT_FILE" + +echo "Coverage report generated as $OUTPUT_FILE" diff --git a/fuzz/flake.lock b/fuzz/flake.lock new file mode 100644 index 0000000..12c0f84 --- /dev/null +++ b/fuzz/flake.lock @@ -0,0 +1,82 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1728241625, + "narHash": "sha256-yumd4fBc/hi8a9QgA9IT8vlQuLZ2oqhkJXHPKxH/tRw=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "c31898adf5a8ed202ce5bea9f347b1c6871f32d1", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1728354625, + "narHash": "sha256-r+Sa1NRRT7LXKzCaVaq75l1GdZcegODtF06uaxVVVbI=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "d216ade5a0091ce60076bf1f8bc816433a1fc5da", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/fuzz/flake.nix b/fuzz/flake.nix new file mode 100644 index 0000000..43cc571 --- /dev/null +++ b/fuzz/flake.nix @@ -0,0 +1,31 @@ +{ + description = "Simple OAuth2 server for hackerspaces"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.flake-utils.follows = "flake-utils"; + }; + }; + outputs = {self, nixpkgs, flake-utils, rust-overlay, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { + inherit system overlays; + }; + in + with pkgs; + { + devShell = mkShell { + buildInputs = [ + (rust-bin.nightly.latest.default.override { extensions = [ "llvm-tools-preview" ]; }) + cargo-fuzz + rustfilt + ]; + }; + }); +} diff --git a/fuzz/fuzz_targets/parser.rs b/fuzz/fuzz_targets/parser.rs new file mode 100644 index 0000000..254a549 --- /dev/null +++ b/fuzz/fuzz_targets/parser.rs @@ -0,0 +1,9 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use zns::{parser::FromBytes, reader::Reader, structs::Message}; + +fuzz_target!(|data: &[u8]| { + let mut reader = Reader::new(data); + let _ = Message::from_bytes(&mut reader); +}); diff --git a/zns/Cargo.toml b/zns/Cargo.toml index 30efe72..78b95bf 100644 --- a/zns/Cargo.toml +++ b/zns/Cargo.toml @@ -11,6 +11,7 @@ test-utils = [] base64 = "0.22.0" int-enum = "1.1" thiserror = "1.0" +arbitrary = { version = "^1.3.2", optional = true, features = ["derive"] } [dev-dependencies] zns = { path = ".", features = ["test-utils"] } diff --git a/zns/src/labelstring.rs b/zns/src/labelstring.rs index f5a758c..96e9e09 100644 --- a/zns/src/labelstring.rs +++ b/zns/src/labelstring.rs @@ -1,6 +1,7 @@ use std::fmt::Display; #[derive(Debug, Clone)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct LabelString(Vec); pub fn labels_equal(vec1: &LabelString, vec2: &LabelString) -> bool { diff --git a/zns/src/reader.rs b/zns/src/reader.rs index 8f708ef..75d78aa 100644 --- a/zns/src/reader.rs +++ b/zns/src/reader.rs @@ -66,12 +66,12 @@ impl<'a> Reader<'a> { } pub fn seek(&self, position: usize) -> Result { - if position >= self.position - 2 { + if self.position < 2 || position >= self.position - 2 { Err(ZNSError::Reader { message: String::from("Seeking into the future is not allowed!!"), }) } else { - let mut reader = Reader::new(&self.buffer[..self.position]); + let mut reader = Reader::new(&self.buffer[..self.position - 1]); reader.position = position; Ok(reader) } @@ -126,7 +126,7 @@ mod tests { let new_reader = reader.seek(1); assert!(new_reader.is_ok()); - assert_eq!(new_reader.unwrap().unread_bytes(), 10); + assert_eq!(new_reader.unwrap().unread_bytes(), 9); let new_reader = reader.seek(100); assert!(new_reader.is_err()); diff --git a/zns/src/structs.rs b/zns/src/structs.rs index 008405c..be0bd4b 100644 --- a/zns/src/structs.rs +++ b/zns/src/structs.rs @@ -3,6 +3,7 @@ use int_enum::IntEnum; use crate::labelstring::LabelString; #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Type { Type(RRType), Other(u16), @@ -10,6 +11,7 @@ pub enum Type { #[repr(u16)] #[derive(Debug, Clone, PartialEq, IntEnum)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum RRType { A = 1, SOA = 6, @@ -20,6 +22,7 @@ pub enum RRType { } #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Class { Class(RRClass), Other(u16), @@ -27,6 +30,7 @@ pub enum Class { #[repr(u16)] #[derive(Debug, Clone, PartialEq, IntEnum)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum RRClass { IN = 1, NONE = 254, @@ -56,6 +60,7 @@ pub enum Opcode { } #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Question { pub qname: LabelString, pub qtype: Type, // NOTE: should be QTYPE, right now not really needed @@ -63,6 +68,7 @@ pub struct Question { } #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Header { pub id: u16, pub flags: u16, // |QR| Opcode |AA|TC|RD|RA| Z | RCODE | ; 1 | 4 | 1 | 1 | 1 | 1 | 3 | 4 @@ -73,6 +79,7 @@ pub struct Header { } #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Message { pub header: Header, pub question: Vec, @@ -82,6 +89,7 @@ pub struct Message { } #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct RR { pub name: LabelString, pub _type: Type,