Skip to content

Commit d1263a2

Browse files
Dimi1010seladb
andauthored
Fixes performance regression during Pcap file reading. (#2072)
* Fixes performance regression during pcap file reading due to extensive allocator pressure. * Try fixes to BGP optional parameter lengths to fix fuzz. * Fix length calculation. * Simplify finding next IAC by using an iterator based search. * Disable fail-fast on regressions as each fuzzer has different sanitizer. * Disable CI Fuzz fail fast as each job provides different error reports. * Try hack to pass fuzz. * Add basic data length checks on constructing DCHP and Vxlan. * Update checks to use canReinterpretAs. * Try increasing the hack buffer... * Fix DHCP getValueAsString relying on recordLen directly, even though getDataSize can potentially have a Type that does not contain the LV part of TLV. Fix potential underflow bug on `getValueAs` when offset is larger than `getDataSize()`. * Temp fix casts. * Fix potential underflow. * Remove temp buffer hack. * Update bgp length guard check. * Harden SSL extension parsing against buffer overruns. * Clamp extension buffer to message length. * Harden SSLServerName hostname extraction against buffer overruns. * Add safety check to insertData. Add possible safety check to DnsLayer addResource. * Cleanup compile errors. * Update docs. * Lint * Increase example test timeout * REVERT: Increase example test timeout * Revert deduplication of SSL Extension parsing procedure. * Lint * Revert getExtType. * Add docs. --------- Co-authored-by: seladb <pcapplusplus@gmail.com>
1 parent 87444bb commit d1263a2

15 files changed

Lines changed: 303 additions & 93 deletions

.github/workflows/build_and_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -999,7 +999,7 @@ jobs:
999999
name: Run ${{ matrix.engine }}-${{ matrix.sanitizer }} fuzzer for regressions
10001000
runs-on: ubuntu-latest
10011001
strategy:
1002-
fail-fast: true
1002+
fail-fast: false
10031003
matrix:
10041004
sanitizer: [address, undefined, memory]
10051005
engine: [libfuzzer]

.github/workflows/cifuzz.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
Fuzzing:
1717
runs-on: ubuntu-latest
1818
strategy:
19-
fail-fast: true
19+
fail-fast: false
2020
matrix:
2121
sanitizer: [address, undefined, memory]
2222
steps:

Packet++/header/DhcpLayer.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,9 @@ namespace pcpp
418418
/// @return DHCP option data as string
419419
std::string getValueAsString(int valueOffset = 0) const
420420
{
421-
if (m_Data == nullptr || m_Data->recordLen - valueOffset < 1)
421+
// TODO: This will burn if valueOffset is negative.
422+
// Should negative offsets even be allowed? Potentially change it to size_t?
423+
if (m_Data == nullptr || getDataSize() < static_cast<size_t>(valueOffset) + 1)
422424
return "";
423425

424426
return std::string(reinterpret_cast<const char*>(m_Data->recordValue) + valueOffset,
@@ -589,6 +591,11 @@ namespace pcpp
589591
/// A destructor for this layer
590592
~DhcpLayer() override = default;
591593

594+
static bool isDataValid(uint8_t const* data, size_t dataLen)
595+
{
596+
return canReinterpretAs<dhcp_header>(data, dataLen);
597+
}
598+
592599
/// Get a pointer to the DHCP header. Notice this points directly to the data, so every change will change the
593600
/// actual packet data
594601
/// @return A pointer to the @ref dhcp_header

Packet++/header/SSLHandshake.h

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <utility>
4+
#include <type_traits>
45
#include "SSLCommon.h"
56
#include "PointerVector.h"
67
#include "Asn1Codec.h"
@@ -99,8 +100,26 @@ namespace pcpp
99100
public:
100101
/// C'tor for this class
101102
/// @param[in] data The raw data for the extension
103+
/// @deprecated This constructor is deprecated because it uses an unbound memory span. Use the constructor with
104+
/// bounded span.
105+
PCPP_DEPRECATED("Unbound memory span. Use the constructor with bounded span.")
102106
explicit SSLExtension(uint8_t* data);
103107

108+
/// @brief Constructs an SSLExtension that interprets the provided data as the raw bytes of the extension.
109+
///
110+
/// The memory span defined by data and dataLen can be larger than the actual extension data.
111+
/// If so, only the bytes corresponding to the extension header (type and length fields) + [length] bytes of
112+
/// extension data will be parsed. The rest of the span will be ignored.
113+
///
114+
/// The above behaviour allows variable length extensions to be parsed, as SSL extensions are laid out
115+
/// sequentially in memory on a payload.
116+
///
117+
/// @param[in] data The raw data for the extension.
118+
/// @param[in] dataLen The length of the data in bytes.
119+
/// @throws std::invalid_argument if data is nullptr or if dataLen is smaller than the size of the extension
120+
/// header (type and length fields).
121+
SSLExtension(uint8_t* data, size_t dataLen);
122+
104123
virtual ~SSLExtension() = default;
105124

106125
/// @return The type of the extension as enum
@@ -118,6 +137,33 @@ namespace pcpp
118137
/// @return A pointer to the raw data of the extension
119138
uint8_t* getData() const;
120139

140+
/// @brief A static method that tries to create an instance of a specific extension type.
141+
///
142+
/// The factory method handles potential buffer overflows by validating that the data length of the span
143+
/// is sufficient to contain the extension header (type and length fields) and the extension data as specified
144+
/// in the length field.
145+
///
146+
/// @tparam T The type of the extension to create. This type must be a class that inherits from SSLExtension.
147+
/// @param data Pointer to the raw data of the extension.
148+
/// @param dataLen Max length of the span to be parsed.
149+
/// @return The extension instance if the data buffer is valid, nullptr otherwise.
150+
template <typename T, typename std::enable_if_t<std::is_base_of<SSLExtension, T>::value, bool> = true>
151+
static std::unique_ptr<T> tryCreateExtension(uint8_t* data, size_t dataLen)
152+
{
153+
if (data == nullptr || dataLen < sizeof(SSLExtensionStruct))
154+
{
155+
return nullptr;
156+
}
157+
158+
auto* extStruct = reinterpret_cast<SSLExtensionStruct*>(data);
159+
if (dataLen < sizeof(SSLExtensionStruct) + extStruct->getDataLength())
160+
{
161+
return nullptr;
162+
}
163+
164+
return std::make_unique<T>(data, dataLen);
165+
}
166+
121167
protected:
122168
/// @struct SSLExtensionStruct
123169
/// Represents the common fields of the extension
@@ -129,9 +175,13 @@ namespace pcpp
129175
uint16_t extensionDataLength;
130176
/// Extension data as raw (byte array)
131177
uint8_t extensionData[];
178+
179+
/// @brief Gets the extension length in host byte order
180+
uint16_t getDataLength() const;
132181
};
133182

134183
uint8_t* m_RawData;
184+
size_t m_RawDataLen;
135185

136186
SSLExtensionStruct* getExtensionStruct() const
137187
{
@@ -145,10 +195,7 @@ namespace pcpp
145195
class SSLServerNameIndicationExtension : public SSLExtension
146196
{
147197
public:
148-
/// C'tor for this class
149-
/// @param[in] data The raw data for the extension
150-
explicit SSLServerNameIndicationExtension(uint8_t* data) : SSLExtension(data)
151-
{}
198+
using SSLExtension::SSLExtension;
152199

153200
/// @return The hostname written in the extension data
154201
std::string getHostName() const;
@@ -160,10 +207,7 @@ namespace pcpp
160207
class SSLSupportedVersionsExtension : public SSLExtension
161208
{
162209
public:
163-
/// C'tor for this class
164-
/// @param[in] data The raw data for the extension
165-
explicit SSLSupportedVersionsExtension(uint8_t* data) : SSLExtension(data)
166-
{}
210+
using SSLExtension::SSLExtension;
167211

168212
/// @return The list of supported versions mentioned in the extension data
169213
std::vector<SSLVersion> getSupportedVersions() const;
@@ -175,10 +219,7 @@ namespace pcpp
175219
class TLSSupportedGroupsExtension : public SSLExtension
176220
{
177221
public:
178-
/// C'tor for this class
179-
/// @param[in] data The raw data for the extension
180-
explicit TLSSupportedGroupsExtension(uint8_t* data) : SSLExtension(data)
181-
{}
222+
using SSLExtension::SSLExtension;
182223

183224
/// @return A vector of the supported groups (also known as "Elliptic Curves")
184225
std::vector<uint16_t> getSupportedGroups() const;
@@ -190,10 +231,7 @@ namespace pcpp
190231
class TLSECPointFormatExtension : public SSLExtension
191232
{
192233
public:
193-
/// C'tor for this class
194-
/// @param[in] data The raw data for the extension
195-
explicit TLSECPointFormatExtension(uint8_t* data) : SSLExtension(data)
196-
{}
234+
using SSLExtension::SSLExtension;
197235

198236
/// @return A vector of the elliptic curves point formats
199237
std::vector<uint8_t> getECPointFormatList() const;

Packet++/header/TLVData.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ namespace pcpp
160160
/// @return The record data as type T
161161
template <typename T> T getValueAs(size_t offset = 0) const
162162
{
163-
if (getDataSize() - offset < sizeof(T))
163+
if (getDataSize() < sizeof(T) + offset)
164164
return 0;
165165

166166
T result;

Packet++/header/VxlanLayer.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ namespace pcpp
9090

9191
~VxlanLayer() override = default;
9292

93+
/// A static method that validates the input data
94+
/// @param[in] data The pointer to the beginning of a byte stream of an Vxlan layer
95+
/// @param[in] dataLen The length of the byte stream
96+
/// @return True if the data is valid and can represent an Vxlan layer
97+
static bool isDataValid(uint8_t const* data, size_t dataLen)
98+
{
99+
return canReinterpretAs<vxlan_header>(data, dataLen);
100+
}
101+
93102
/// Get a pointer to the VXLAN header. Notice this points directly to the data, so every change will change the
94103
/// actual packet data
95104
/// @return A pointer to the vxlan_header

Packet++/src/BgpLayer.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,7 @@ namespace pcpp
263263
return;
264264
}
265265

266-
size_t optionalParamsLen = (size_t)be16toh(msgHdr->optionalParameterLength);
267-
266+
size_t optionalParamsLen = msgHdr->optionalParameterLength;
268267
if (optionalParamsLen > getHeaderLen() - sizeof(bgp_open_message))
269268
{
270269
optionalParamsLen = getHeaderLen() - sizeof(bgp_open_message);
@@ -301,7 +300,18 @@ namespace pcpp
301300
bgp_open_message* msgHdr = getOpenMsgHeader();
302301
if (msgHdr != nullptr)
303302
{
304-
return (size_t)(msgHdr->optionalParameterLength);
303+
auto optParamLen = static_cast<size_t>(msgHdr->optionalParameterLength);
304+
305+
constexpr size_t bgpOpenMsgHeaderSize = sizeof(bgp_open_message);
306+
size_t headerLen = getHeaderLen();
307+
if (headerLen < optParamLen + bgpOpenMsgHeaderSize)
308+
{
309+
PCPP_LOG_WARN("BGP Layer optional param length exceeds total BGP message. "
310+
"This packet might be malformed! Trimming to maximum allowed by BGP message length.");
311+
optParamLen = headerLen >= bgpOpenMsgHeaderSize ? headerLen - bgpOpenMsgHeaderSize : 0;
312+
}
313+
314+
return optParamLen;
305315
}
306316

307317
return 0;

Packet++/src/DnsLayer.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,15 @@ namespace pcpp
478478
while (curResource != nullptr && curResource->getType() <= resType)
479479
{
480480
newResourceOffsetInLayer += curResource->getSize();
481+
482+
if (newResourceOffsetInLayer > m_DataLen)
483+
{
484+
// This possibly means that the DNS layer has been created from a malformed packet.
485+
PCPP_LOG_ERROR("Couldn't add resource! DNS Layer is malformed and contains out of bounds resources.");
486+
delete newResource;
487+
return nullptr;
488+
}
489+
481490
IDnsResource* nextResource = curResource->getNextResource();
482491
if (nextResource == nullptr || nextResource->getType() > resType)
483492
break;

Packet++/src/RawPacket.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ namespace pcpp
139139

140140
void RawPacket::insertData(int atIndex, const uint8_t* dataToInsert, size_t dataToInsertLen)
141141
{
142+
if (atIndex > m_RawDataLen)
143+
{
144+
throw std::out_of_range("Insert index is out of raw packet bound. Inserts can only happen in range [0, " +
145+
std::to_string(m_RawDataLen) + ']');
146+
}
147+
142148
// memmove copies data as if there was an intermediate buffer in between - so it allows for copying processes on
143149
// overlapping src/dest ptrs if insertData is called with atIndex == m_RawDataLen, then no data is being moved.
144150
// The data of the raw packet is still extended by dataToInsertLen

0 commit comments

Comments
 (0)