Skip to content
Open
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
33 changes: 33 additions & 0 deletions projects/cpp-libp2p/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################

FROM gcr.io/oss-fuzz-base/base-builder

# Build prerequisites commonly needed by C++ projects
RUN apt-get update && apt-get install -y \
make cmake ninja-build pkg-config git python3 \
&& rm -rf /var/lib/apt/lists/*

# Fetch project sources
RUN git clone --depth 1 https://github.com/libp2p/cpp-libp2p.git cpp-libp2p

WORKDIR $SRC/cpp-libp2p

# Copy build script and fuzzers
COPY build.sh $SRC/
COPY *.cc $SRC/
COPY project.yaml $SRC/

116 changes: 116 additions & 0 deletions projects/cpp-libp2p/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/bin/bash -eu
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################

set -o pipefail

export CXXFLAGS="${CXXFLAGS} -std=c++20"
export CFLAGS="${CFLAGS}"

# Build cpp-libp2p with minimal options (no tests/examples)
mkdir -p $SRC/cpp-libp2p/build
cd $SRC/cpp-libp2p/build

cmake -G "Ninja" \
-DCMAKE_BUILD_TYPE=Release \
-DTESTING=OFF \
-DEXAMPLES=OFF \
-DCMAKE_CXX_FLAGS="${CXXFLAGS}" \
-DCMAKE_C_FLAGS="${CFLAGS}" \
..

ninja -j"$(nproc)"

cd $SRC

# Helper to locate built static libraries
find_lib() {
local name="$1"; shift || true
local found
found=$(find "$SRC/cpp-libp2p/build" -name "lib${name}.a" -print -quit)
if [ -z "$found" ]; then
echo "Failed to find lib${name}.a" >&2
exit 1
fi
echo "$found"
}

INCLUDE_FLAGS=(
-I"$SRC/cpp-libp2p/include"
)

# Build fuzzers

# 1) Multibase decode fuzzing
$CXX ${CXXFLAGS} \
multibase_decode_fuzzer.cc \
"${INCLUDE_FLAGS[@]}" \
$(find_lib p2p_multibase_codec) \
$LIB_FUZZING_ENGINE -lpthread -o $OUT/multibase_decode_fuzzer

# 2) Multihash parse fuzzing
$CXX ${CXXFLAGS} \
multihash_parse_fuzzer.cc \
"${INCLUDE_FLAGS[@]}" \
$(find_lib p2p_multihash) \
$(find_lib p2p_varint_prefix_reader) \
$LIB_FUZZING_ENGINE -lpthread -o $OUT/multihash_parse_fuzzer

# 3) Multiaddress parse fuzzing
$CXX ${CXXFLAGS} \
multiaddress_parse_fuzzer.cc \
"${INCLUDE_FLAGS[@]}" \
$(find_lib p2p_multiaddress) \
$(find_lib p2p_converters) \
$(find_lib p2p_uvarint) \
$(find_lib p2p_byteutil) \
$(find_lib p2p_multibase_codec) \
$LIB_FUZZING_ENGINE -lpthread -o $OUT/multiaddress_parse_fuzzer

# 4) Multiselect parser fuzzing (backup)
$CXX ${CXXFLAGS} \
multiselect_parser_fuzzer.cc \
"${INCLUDE_FLAGS[@]}" \
$(find_lib p2p_multiselect) \
$(find_lib p2p_read_buffer) \
$(find_lib p2p_varint_prefix_reader) \
$(find_lib p2p_logger) \
$(find "$SRC/cpp-libp2p/build" -name 'libsoralog*.a' -print) \
$(find "$SRC/cpp-libp2p/build" -name 'libyaml-cpp*.a' -print | head -n 1) \
$(find "$SRC/cpp-libp2p/build" -name 'libfmt*.a' -print | head -n 1) \
$LIB_FUZZING_ENGINE -lpthread -o $OUT/multiselect_parser_fuzzer

# 5) BigEndian MessageReadWriter write UAF (backup)
$CXX ${CXXFLAGS} \
message_read_writer_bigendian_write_uaf_fuzzer.cc \
"${INCLUDE_FLAGS[@]}" \
$(find_lib p2p_message_read_writer) \
$(find_lib p2p_message_read_writer_error) \
$(find_lib p2p_varint_reader) \
$(find_lib p2p_uvarint) \
$LIB_FUZZING_ENGINE -lpthread -o $OUT/message_read_writer_bigendian_write_uaf_fuzzer

# 6) BigEndian MessageReadWriter read length (backup)
$CXX ${CXXFLAGS} \
message_read_writer_bigendian_read_len_fuzzer.cc \
"${INCLUDE_FLAGS[@]}" \
$(find_lib p2p_message_read_writer) \
$(find_lib p2p_message_read_writer_error) \
$(find_lib p2p_varint_reader) \
$(find_lib p2p_uvarint) \
$LIB_FUZZING_ENGINE -lpthread -o $OUT/message_read_writer_bigendian_read_len_fuzzer

# Seed corpora can be added later; current fuzzers start without seeds.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstddef>
#include <cstdint>
#include <span>
#include <memory>

#include <libp2p/common/types.hpp>
#include <libp2p/outcome/outcome.hpp>
#include <libp2p/basic/readwriter.hpp>
#include <libp2p/basic/message_read_writer_bigendian.hpp>

using libp2p::BytesIn;
using libp2p::BytesOut;
using libp2p::basic::MessageReadWriterBigEndian;

class PrefixReader : public libp2p::basic::ReadWriter {
public:
PrefixReader(const uint8_t *data, size_t size) : data_(data), size_(size) {}

void readSome(BytesOut out, ReadCallbackFunc cb) override {
if (phase_ == 0) {
// Supply exactly kLenMarkerSize bytes (or zeros if insufficient input)
for (size_t i = 0; i < out.size(); ++i) {
uint8_t b = 0;
if (pos_ < size_) {
b = data_[pos_++];
}
out[i] = b;
}
phase_ = 1;
cb(out.size());
} else {
// Return error on subsequent reads to avoid full payload
cb(std::errc::message_size);
}
}

void deferReadCallback(outcome::result<size_t> res,
ReadCallbackFunc cb) override {
cb(res);
}

void writeSome(BytesIn in, WriteCallbackFunc cb) override {
// Not used; pretend success
cb(in.size());
}

void deferWriteCallback(std::error_code ec, WriteCallbackFunc cb) override {
if (ec) {
cb(ec);
}
}

private:
const uint8_t *data_;
size_t size_;
size_t pos_ = 0;
int phase_ = 0;
};

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
auto conn = std::make_shared<PrefixReader>(data, size);
auto mrw = std::make_shared<MessageReadWriterBigEndian>(conn);

mrw->read([](libp2p::basic::MessageReadWriter::ReadCallback) {
// ignore result
});
return 0;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstddef>
#include <cstdint>
#include <thread>
#include <vector>
#include <memory>

#include <libp2p/common/types.hpp>
#include <libp2p/outcome/outcome.hpp>
#include <libp2p/basic/readwriter.hpp>
#include <libp2p/basic/message_read_writer_bigendian.hpp>

using libp2p::BytesIn;
using libp2p::basic::MessageReadWriterBigEndian;

// Fake ReadWriter that performs asynchronous write and reads from the span
// after the initiator returns, to expose UAF if the span points to a local.
class DelayedWriter : public libp2p::basic::ReadWriter {
public:
void readSome(libp2p::BytesOut /*out*/, ReadCallbackFunc cb) override {
// Not used; return error to avoid recursion
cb(std::errc::operation_not_supported);
}

void deferReadCallback(outcome::result<size_t> res,
ReadCallbackFunc cb) override {
cb(res);
}

void writeSome(BytesIn in, WriteCallbackFunc cb) override {
// Defer the callback and touch the span after caller returns
threads_.emplace_back([in, cb]() mutable {
// Copy from the span (dereferences pointer that may be dangling)
std::vector<uint8_t> copy(in.begin(), in.end());
// Report all bytes written to terminate libp2p::write recursion
cb(copy.size());
});
}

void deferWriteCallback(std::error_code ec, WriteCallbackFunc cb) override {
if (ec) {
cb(ec);
}
}

void joinAll() {
for (auto &t : threads_) {
if (t.joinable()) {
t.join();
}
}
threads_.clear();
}

private:
std::vector<std::thread> threads_;
};

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size == 0) {
return 0;
}

auto conn = std::make_shared<DelayedWriter>();
auto mrw = std::make_shared<MessageReadWriterBigEndian>(conn);

BytesIn in(data, size);
mrw->write(in, [](outcome::result<void> res) { (void)res; });

// Ensure the deferred thread runs and touches the span
conn->joinAll();
return 0;
}

48 changes: 48 additions & 0 deletions projects/cpp-libp2p/multiaddress_parse_fuzzer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstddef>
#include <cstdint>
#include <string>

#include <libp2p/multi/multiaddress.hpp>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (data == nullptr || size == 0) {
return 0;
}
std::string s(reinterpret_cast<const char *>(data), size);

// Multiaddress strings typically start with '/'. Ensure both paths are tested.
auto try_parse = [](std::string_view str) {
auto res = libp2p::multi::Multiaddress::create(str);
if (res) {
const auto &ma = res.value();
(void)ma.getStringAddress();
(void)ma.getBytesAddress();
(void)ma.getPeerId();
(void)ma.getProtocols();
(void)ma.getProtocolsWithValues();
(void)ma.splitFirst();
}
};

try_parse(s);
if (!s.empty() && s.front() != '/') {
s.insert(s.begin(), '/');
try_parse(s);
}
return 0;
}

Loading