Skip to content
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
7 changes: 0 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

env:
CARGO_TERM_COLOR: always
Expand All @@ -13,12 +12,6 @@ env:
VERBOSE: 1

jobs:
spell-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: crate-ci/typos@master

build-ubuntu:
runs-on: ubuntu-latest
steps:
Expand Down
45 changes: 45 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Fuzz

on:
push:
branches: [main]
pull_request:

jobs:
fuzz:
runs-on: ubuntu-latest
strategy:
# Run every target even if one finds a crash
fail-fast: false
matrix:
target:
- dns
- ether
- icmp
- icmpv6
- ipv4
- ipv6
- tcp
- udp
- vlan
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- uses: taiki-e/install-action@v2
with:
tool: cargo-fuzz
- uses: Swatinem/rust-cache@v2
with:
workspaces: fuzz
# 15 minutes per target on pushes to main, 1 minute on pull requests.
- run: |
cargo +nightly fuzz run --target x86_64-unknown-linux-gnu ${{ matrix.target }} -- \
-max_total_time=${{ github.event_name == 'push' && '900' || '60' }}
# Preserve the crashing input so the failure can be reproduced locally
# with `cargo +nightly fuzz run <target> <artifact>`.
- if: failure()
uses: actions/upload-artifact@v4
with:
name: fuzz-artifacts-${{ matrix.target }}
path: fuzz/artifacts/${{ matrix.target }}/
if-no-files-found: ignore
4 changes: 4 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,9 @@ fn print_link_search_path() {
fn print_link_search_path() {}

fn main() {
// `pnettest` holds live-network integration tests that require elevated
// privileges and real interfaces; gate them behind an opt-in custom cfg.
// Declare it so `unexpected_cfgs` doesn't flag the `#[cfg(pnet_test_network)]`.
println!("cargo::rustc-check-cfg=cfg(pnet_test_network)");
print_link_search_path();
}
21 changes: 17 additions & 4 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ name = "pnet-fuzz"
version = "0.0.1"
authors = ["Automatically generated"]
publish = false
edition = "2021"

[package.metadata]
cargo-fuzz = true
Expand All @@ -25,6 +26,10 @@ path = "fuzzers/ether.rs"
name = "ipv4"
path = "fuzzers/ipv4.rs"

[[bin]]
name = "ipv6"
path = "fuzzers/ipv6.rs"

[[bin]]
name = "tcp"
path = "fuzzers/tcp.rs"
Expand All @@ -33,10 +38,18 @@ path = "fuzzers/tcp.rs"
name = "udp"
path = "fuzzers/udp.rs"

[[bin]]
name = "gre"
path = "fuzzers/gre.rs"

[[bin]]
name = "vlan"
path = "fuzzers/vlan.rs"

[[bin]]
name = "icmp"
path = "fuzzers/icmp.rs"

[[bin]]
name = "icmpv6"
path = "fuzzers/icmpv6.rs"

[[bin]]
name = "dns"
path = "fuzzers/dns.rs"
78 changes: 78 additions & 0 deletions fuzz/fuzzers/dns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#![no_main]

use std::hint::black_box;

use libfuzzer_sys::fuzz_target;
use pnet::packet::dns::{DnsPacket, DnsQuery, DnsResponse};
use pnet::packet::{FromPacket, Packet, PacketSize};

fn drive_query(q: &DnsQuery) {
for b in q.qname.iter() {
black_box(*b);
}
black_box(q.qtype);
black_box(q.qclass);
for b in q.payload.iter() {
black_box(*b);
}
// Unchecked label parsing: indexes/slices `qname` and unwraps from_utf8.
black_box(q.get_qname_parsed());
}

fn drive_response(r: &DnsResponse) {
black_box(r.name_tag);
black_box(r.rtype);
black_box(r.rclass);
black_box(r.ttl);
black_box(r.data_len);
for b in r.data.iter() {
black_box(*b);
}
for b in r.payload.iter() {
black_box(*b);
}
}

fuzz_target!(|data: &[u8]| {
if let Some(dns) = DnsPacket::new(data) {
// Fixed header. get_opcode()/get_rcode() map a u4 onto a small enum and
// panic (unreachable!) on unmapped values; get_*_count drive the
// variable-length section parsers below.
black_box(dns.get_id());
black_box(dns.get_is_response());
black_box(dns.get_opcode());
black_box(dns.get_is_authoriative());
black_box(dns.get_is_truncated());
black_box(dns.get_is_recursion_desirable());
black_box(dns.get_is_recursion_available());
black_box(dns.get_zero_reserved());
black_box(dns.get_is_answer_authenticated());
black_box(dns.get_is_non_authenticated_data());
black_box(dns.get_rcode());
black_box(dns.get_query_count());
black_box(dns.get_response_count());
black_box(dns.get_authority_rr_count());
black_box(dns.get_additional_rr_count());

// Variable-length sections. Each call parses the section and the
// per-record label/length fields.
for q in dns.get_queries() {
drive_query(&q);
}
for r in dns.get_responses() {
drive_response(&r);
}
for r in dns.get_authorities() {
drive_response(&r);
}
for r in dns.get_additional() {
drive_response(&r);
}

for b in dns.payload().iter() {
black_box(*b);
}
black_box(dns.packet_size());
black_box(dns.from_packet());
}
});
22 changes: 10 additions & 12 deletions fuzz/fuzzers/ether.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
#![no_main]
extern crate libfuzzer_sys;
extern crate pnet;

use std::hint::black_box;

use libfuzzer_sys::fuzz_target;
use pnet::packet::Packet;
use pnet::packet::ethernet::EthernetPacket;

#[export_name="rust_fuzzer_test_input"]
pub extern fn go(data: &[u8]) {
fuzz_target!(|data: &[u8]| {
if let Some(eth) = EthernetPacket::new(data) {
let _s = eth.get_source();
let _d = eth.get_destination();
let _t = eth.get_ethertype();
let pl = eth.payload();
for b in pl.iter() {
*b;
black_box(eth.get_source());
black_box(eth.get_destination());
black_box(eth.get_ethertype());
for b in eth.payload().iter() {
black_box(*b);
}
}

}
});
32 changes: 0 additions & 32 deletions fuzz/fuzzers/gre.rs

This file was deleted.

77 changes: 77 additions & 0 deletions fuzz/fuzzers/icmp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#![no_main]

use std::hint::black_box;

use libfuzzer_sys::fuzz_target;
use pnet::packet::icmp::destination_unreachable::DestinationUnreachablePacket;
use pnet::packet::icmp::echo_reply::EchoReplyPacket;
use pnet::packet::icmp::echo_request::EchoRequestPacket;
use pnet::packet::icmp::time_exceeded::TimeExceededPacket;
use pnet::packet::icmp::{self, IcmpPacket};
use pnet::packet::{FromPacket, Packet, PacketSize};

fuzz_target!(|data: &[u8]| {
if let Some(icmp) = IcmpPacket::new(data) {
black_box(icmp.get_icmp_type());
black_box(icmp.get_icmp_code());
black_box(icmp.get_checksum());
for b in icmp.payload().iter() {
black_box(*b);
}
black_box(icmp.packet_size());
black_box(icmp.from_packet());
black_box(icmp::checksum(&icmp));
}

// Drive every type-specific ICMP parser from the same buffer.
if let Some(p) = EchoReplyPacket::new(data) {
black_box(p.get_icmp_type());
black_box(p.get_icmp_code());
black_box(p.get_checksum());
black_box(p.get_identifier());
black_box(p.get_sequence_number());
for b in p.payload().iter() {
black_box(*b);
}
black_box(p.packet_size());
black_box(p.from_packet());
}

if let Some(p) = EchoRequestPacket::new(data) {
black_box(p.get_icmp_type());
black_box(p.get_icmp_code());
black_box(p.get_checksum());
black_box(p.get_identifier());
black_box(p.get_sequence_number());
for b in p.payload().iter() {
black_box(*b);
}
black_box(p.packet_size());
black_box(p.from_packet());
}

if let Some(p) = DestinationUnreachablePacket::new(data) {
black_box(p.get_icmp_type());
black_box(p.get_icmp_code());
black_box(p.get_checksum());
black_box(p.get_unused());
black_box(p.get_next_hop_mtu());
for b in p.payload().iter() {
black_box(*b);
}
black_box(p.packet_size());
black_box(p.from_packet());
}

if let Some(p) = TimeExceededPacket::new(data) {
black_box(p.get_icmp_type());
black_box(p.get_icmp_code());
black_box(p.get_checksum());
black_box(p.get_unused());
for b in p.payload().iter() {
black_box(*b);
}
black_box(p.packet_size());
black_box(p.from_packet());
}
});
Loading
Loading