Bug description
Summary
asan_reports.tar.gz
pocs.tar.gz
AddressSanitizer detected two memory safety bugs in the DHCP layer’s TLV option parsing code: Heap-use-after-free and Heap-buffer-overflow in pcpp::DhcpOption::canAssign (called from option lookup). These issues can be reliably triggered by malformed or truncated DHCP packets.
Root Cause Analysis
Suspected Problematic Code
static bool canAssign(const uint8_t* recordRawData, size_t tlvDataLen)
{
auto data = reinterpret_cast<TLVRawData const*>(recordRawData);
if (data == nullptr)
return false;
if (tlvDataLen < sizeof(TLVRawData::recordType))
return false;
if (data->recordType == static_cast<uint8_t>(DHCPOPT_END) ||
data->recordType == static_cast<uint8_t>(DHCPOPT_PAD))
return true;
return TLVRecord<uint8_t, uint8_t>::canAssign(recordRawData, tlvDataLen);
}
The line if (data->recordType == ...) accesses recordType without confirming the buffer is valid for that read, potentially causing OOB or UAF if the buffer is short or dangling.
Possible Explanation
- The code accesses the first byte of
recordRawData before checking whether tlvDataLen is at least 1.
- TLV parsing utilities sometimes operate on already-freed memory (use-after-free), or on buffers too short to hold the minimum record, leading to both UAF and OOB.
Suggested Fix
- At the start of
canAssign, ensure tlvDataLen >= sizeof(TLVRawData::recordType) before reading recordType.
Platform
- OS:
Ubuntu 22.04.5 LTS
- Clang version:
Ubuntu clang version 16.0.6 (++20231112100510+7cbf1a259152-1~exp1~20231112100554.106)
- Version commit
0588d88eca769a02e660161f8965ef6152ada90e
Steps to Reproduce
export SRC=/src OUT=/out
mkdir $SRC $OUT
apt-get update && apt-get install -y cmake autoconf flex bison zip
export CC=clang CXX=clang++ CFLAGS='-fsanitize=address -g -O1' CXXFLAGS='-fsanitize=address -g -O1' LDFLAGS="-fuse-ld=lld" LIB_FUZZING_ENGINE='-fsanitize=fuzzer'
git clone --depth=1 https://github.com/seladb/PcapPlusPlus PcapPlusPlus
git clone --depth=1 https://github.com/the-tcpdump-group/tcpdump.git tcpdump
git clone --depth=1 https://github.com/the-tcpdump-group/libpcap.git libpcap
cd $SRC/PcapPlusPlus
$SRC/PcapPlusPlus/Tests/Fuzzers/ossfuzz.sh
$OUT/FuzzTargetNg poc
ASAN report
Use-after-free:
=================================================================
==530049==ERROR: AddressSanitizer: heap-use-after-free on address 0x60c00000039a at pc 0x57541208e670 bp 0x7ffc26097450 sp 0x7ffc26097448
READ of size 1 at 0x60c00000039a thread T0
#0 0x57541208e66f in pcpp::DhcpOption::canAssign(unsigned char const*, unsigned long) /src/PcapPlusPlus/Packet++/header/DhcpLayer.h:459:14
#1 0x57541208e66f in pcpp::TLVRecordReader<pcpp::DhcpOption>::getFirstTLVRecord(unsigned char*, unsigned long) const /src/PcapPlusPlus/Packet++/header/TLVData.h:241:9
#2 0x57541208e66f in pcpp::TLVRecordReader<pcpp::DhcpOption>::getTLVRecord(unsigned int, unsigned char*, unsigned long) const /src/PcapPlusPlus/Packet++/header/TLVData.h:299:27
#3 0x57541208b751 in pcpp::DhcpLayer::getOptionData(pcpp::DhcpOptionTypes) const /src/PcapPlusPlus/Packet++/src/DhcpLayer.cpp:204:25
#4 0x57541208b751 in pcpp::DhcpLayer::getMessageType() const /src/PcapPlusPlus/Packet++/src/DhcpLayer.cpp:178:20
#5 0x57541208b751 in pcpp::DhcpLayer::toString[abi:cxx11]() const /src/PcapPlusPlus/Packet++/src/DhcpLayer.cpp:127:11
#6 0x575412028977 in LLVMFuzzerTestOneInput /src/PcapPlusPlus/Tests/Fuzzers/FuzzTarget.cpp:64:25
#7 0x575411f37e90 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) crtstuff.c
#8 0x575411f21d6f in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) crtstuff.c
#9 0x575411f27826 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) crtstuff.c
#10 0x575411f50cf2 in main (/out/FuzzTargetNg+0x1a7cf2) (BuildId: 97bfbeafd983e755)
#11 0x773d41b30d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#12 0x773d41b30e3f in __libc_start_main csu/../csu/libc-start.c:392:3
#13 0x575411f1cc74 in _start (/out/FuzzTargetNg+0x173c74) (BuildId: 97bfbeafd983e755)
0x60c00000039a is located 90 bytes inside of 121-byte region [0x60c000000340,0x60c0000003b9)
freed by thread T0 here:
#0 0x575412025c5d in operator delete(void*) (/out/FuzzTargetNg+0x27cc5d) (BuildId: 97bfbeafd983e755)
#1 0x575412139ebc in __gnu_cxx::new_allocator<char>::deallocate(char*, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/ext/new_allocator.h:145:2
#2 0x575412139ebc in std::allocator_traits<std::allocator<char>>::deallocate(std::allocator<char>&, char*, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/alloc_traits.h:496:13
#3 0x575412139ebc in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::_M_destroy(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/basic_string.h:245:9
#4 0x575412139ebc in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::_M_dispose() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/basic_string.h:240:4
#5 0x575412139ebc in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::~basic_string() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/basic_string.h:672:9
#6 0x575412139ebc in void std::_Destroy<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_construct.h:151:19
#7 0x575412139ebc in void std::_Destroy_aux<false>::__destroy<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_construct.h:163:6
#8 0x575412139ebc in void std::_Destroy<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_construct.h:195:7
#9 0x575412139ebc in void std::_Destroy<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>&) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/alloc_traits.h:848:7
#10 0x575412139ebc in std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>>::~vector() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_vector.h:680:2
#11 0x575412139ebc in pcpp::Packet::toString[abi:cxx11](bool) const /src/PcapPlusPlus/Packet++/src/Packet.cpp:832:2
#12 0x5754120288d9 in LLVMFuzzerTestOneInput /src/PcapPlusPlus/Tests/Fuzzers/FuzzTarget.cpp:60:17
#13 0x575411f37e90 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) crtstuff.c
#14 0x575411f21d6f in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) crtstuff.c
#15 0x575411f27826 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) crtstuff.c
#16 0x575411f50cf2 in main (/out/FuzzTargetNg+0x1a7cf2) (BuildId: 97bfbeafd983e755)
#17 0x773d41b30d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
previously allocated by thread T0 here:
#0 0x5754120253fd in operator new(unsigned long) (/out/FuzzTargetNg+0x27c3fd) (BuildId: 97bfbeafd983e755)
#1 0x773d41f970bd in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) (/lib/x86_64-linux-gnu/libstdc++.so.6+0x14c0bd) (BuildId: e37fe1a879783838de78cbc8c80621fa685d58a2)
SUMMARY: AddressSanitizer: heap-use-after-free /src/PcapPlusPlus/Packet++/header/DhcpLayer.h:459:14 in pcpp::DhcpOption::canAssign(unsigned char const*, unsigned long)
Shadow bytes around the buggy address:
0x60c000000100: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x60c000000180: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
0x60c000000200: fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa
0x60c000000280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x60c000000300: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
=>0x60c000000380: fd fd fd[fd]fd fd fd fd fa fa fa fa fa fa fa fa
0x60c000000400: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x60c000000480: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
0x60c000000500: fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa
0x60c000000580: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x60c000000600: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==530049==ABORTING
Buffer-overflow:
=================================================================
==530177==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6120000008da at pc 0x5c6caf0c3670 bp 0x7ffe463706f0 sp 0x7ffe463706e8
READ of size 1 at 0x6120000008da thread T0
#0 0x5c6caf0c366f in pcpp::DhcpOption::canAssign(unsigned char const*, unsigned long) /src/PcapPlusPlus/Packet++/header/DhcpLayer.h:459:14
#1 0x5c6caf0c366f in pcpp::TLVRecordReader<pcpp::DhcpOption>::getFirstTLVRecord(unsigned char*, unsigned long) const /src/PcapPlusPlus/Packet++/header/TLVData.h:241:9
#2 0x5c6caf0c366f in pcpp::TLVRecordReader<pcpp::DhcpOption>::getTLVRecord(unsigned int, unsigned char*, unsigned long) const /src/PcapPlusPlus/Packet++/header/TLVData.h:299:27
#3 0x5c6caf0c0751 in pcpp::DhcpLayer::getOptionData(pcpp::DhcpOptionTypes) const /src/PcapPlusPlus/Packet++/src/DhcpLayer.cpp:204:25
#4 0x5c6caf0c0751 in pcpp::DhcpLayer::getMessageType() const /src/PcapPlusPlus/Packet++/src/DhcpLayer.cpp:178:20
#5 0x5c6caf0c0751 in pcpp::DhcpLayer::toString[abi:cxx11]() const /src/PcapPlusPlus/Packet++/src/DhcpLayer.cpp:127:11
#6 0x5c6caf16f70e in pcpp::Packet::toStringList(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>>&, bool) const /src/PcapPlusPlus/Packet++/src/Packet.cpp:841:31
#7 0x5c6caf16e326 in pcpp::Packet::toString[abi:cxx11](bool) const /src/PcapPlusPlus/Packet++/src/Packet.cpp:829:3
#8 0x5c6caf05d8d9 in LLVMFuzzerTestOneInput /src/PcapPlusPlus/Tests/Fuzzers/FuzzTarget.cpp:60:17
#9 0x5c6caef6ce90 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) crtstuff.c
#10 0x5c6caef56d6f in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) crtstuff.c
#11 0x5c6caef5c826 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) crtstuff.c
#12 0x5c6caef85cf2 in main (/out/FuzzTargetNg+0x1a7cf2) (BuildId: 97bfbeafd983e755)
#13 0x7438ed258d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#14 0x7438ed258e3f in __libc_start_main csu/../csu/libc-start.c:392:3
#15 0x5c6caef51c74 in _start (/out/FuzzTargetNg+0x173c74) (BuildId: 97bfbeafd983e755)
0x6120000008da is located 8 bytes after 274-byte region [0x6120000007c0,0x6120000008d2)
allocated by thread T0 here:
#0 0x5c6caf05a50d in operator new[](unsigned long) (/out/FuzzTargetNg+0x27c50d) (BuildId: 97bfbeafd983e755)
#1 0x5c6caf0754f4 in pcpp::PcapNgFileReaderDevice::getNextPacket(pcpp::RawPacket&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>&) /src/PcapPlusPlus/Pcap++/src/PcapFileDevice.cpp:437:27
#2 0x5c6caf0771ae in pcpp::PcapNgFileReaderDevice::getNextPacket(pcpp::RawPacket&) /src/PcapPlusPlus/Pcap++/src/PcapFileDevice.cpp:462:10
#3 0x5c6caf05dc36 in LLVMFuzzerTestOneInput /src/PcapPlusPlus/Tests/Fuzzers/FuzzTarget.cpp:71:19
#4 0x5c6caef6ce90 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) crtstuff.c
#5 0x5c6caef56d6f in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) crtstuff.c
#6 0x5c6caef5c826 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) crtstuff.c
#7 0x5c6caef85cf2 in main (/out/FuzzTargetNg+0x1a7cf2) (BuildId: 97bfbeafd983e755)
#8 0x7438ed258d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
SUMMARY: AddressSanitizer: heap-buffer-overflow /src/PcapPlusPlus/Packet++/header/DhcpLayer.h:459:14 in pcpp::DhcpOption::canAssign(unsigned char const*, unsigned long)
Shadow bytes around the buggy address:
0x612000000600: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
0x612000000680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x612000000700: 00 00 00 00 00 00 00 00 00 00 00 00 00 fa fa fa
0x612000000780: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
0x612000000800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x612000000880: 00 00 00 00 00 00 00 00 00 00 02[fa]fa fa fa fa
0x612000000900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x612000000980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x612000000a00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x612000000a80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x612000000b00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==530177==ABORTING
PcapPlusPlus versions tested on
PcapPlusPlus master branch
Other PcapPlusPlus version (if applicable)
hash: 0588d88eca769a02e660161f8965ef6152ada90e
Operating systems tested on
Linux
Other operation systems (if applicable)
No response
Compiler version
clang version 16.0.6
Packet capture backend (if applicable)
No response
Bug description
Summary
asan_reports.tar.gz
pocs.tar.gz
AddressSanitizer detected two memory safety bugs in the DHCP layer’s TLV option parsing code: Heap-use-after-free and Heap-buffer-overflow in
pcpp::DhcpOption::canAssign(called from option lookup). These issues can be reliably triggered by malformed or truncated DHCP packets.Root Cause Analysis
Suspected Problematic Code
The line
if (data->recordType == ...)accessesrecordTypewithout confirming the buffer is valid for that read, potentially causing OOB or UAF if the buffer is short or dangling.Possible Explanation
recordRawDatabefore checking whethertlvDataLenis at least 1.Suggested Fix
canAssign, ensuretlvDataLen >= sizeof(TLVRawData::recordType)before readingrecordType.Platform
Ubuntu 22.04.5 LTSUbuntu clang version 16.0.6 (++20231112100510+7cbf1a259152-1~exp1~20231112100554.106)0588d88eca769a02e660161f8965ef6152ada90eSteps to Reproduce
ASAN report
Use-after-free:
Buffer-overflow:
PcapPlusPlus versions tested on
PcapPlusPlus master branch
Other PcapPlusPlus version (if applicable)
hash:
0588d88eca769a02e660161f8965ef6152ada90eOperating systems tested on
Linux
Other operation systems (if applicable)
No response
Compiler version
clang version 16.0.6
Packet capture backend (if applicable)
No response