Skip to content

Fix heap-buffer-overflow in BgpOpenMessageLayer::getOptionalParameters() (#1865)#2143

Merged
seladb merged 9 commits into
seladb:devfrom
alacrity-aya:fix/bgp-open-optional-params-heap-overflow
May 27, 2026
Merged

Fix heap-buffer-overflow in BgpOpenMessageLayer::getOptionalParameters() (#1865)#2143
seladb merged 9 commits into
seladb:devfrom
alacrity-aya:fix/bgp-open-optional-params-heap-overflow

Conversation

@alacrity-aya

Copy link
Copy Markdown
Contributor

Fix heap-buffer-overflow in BgpOpenMessageLayer::getOptionalParameters()

Fixes #1865

Problem

Multiple heap-buffer-overflow vulnerabilities were detected via fuzzing in BgpOpenMessageLayer::getOptionalParameters() in BgpLayer.cpp. When parsing malformed or truncated BGP OPEN messages, the following out-of-bounds reads could occur:

  1. dataPtr[0] / dataPtr[1] read OOB — The loop entered whenever byteCount < optionalParamsLen, but never verified that at least 2 bytes remained to safely read the type and length fields.

  2. Incorrect bounds check for individual parameter length — The old check op.length > optionalParamsLen - byteCount did not account for the 2-byte type+length header itself, so a parameter with op.length = 0 could still cause totalLen = 2 to push dataPtr past the end of the buffer.

  3. No guard on headerLen vs sizeof(bgp_open_message) — If the BGP message was too short to hold a full bgp_open_message struct, arithmetic like headerLen - sizeof(bgp_open_message) would underflow.

The same lack of header-size guard also existed in getOptionalParametersLength() and setOptionalParameters().

Additionally, the optional_parameter default constructor had a FIXME note indicating it did not actually zero-initialize its fields.

Root Cause

// OLD — missing guard before reading dataPtr[0]/dataPtr[1]
while (byteCount < optionalParamsLen) {
    optional_parameter op;
    op.type = dataPtr[0];   // OOB if < 2 bytes remain
    op.length = dataPtr[1]; // OOB if < 2 bytes remain

    // BUG: check excluded the 2-byte header, so op.length = 1 with 2 bytes
    // remaining would pass, then totalLen = 3 > remaining
    if (op.length > optionalParamsLen - byteCount) { break; }
    ...
}

Fix

  • getOptionalParameters(): Add early return when headerLen < sizeof(bgp_open_message). Inside the loop, check remaining < optParamHdrSize (2 bytes) before reading type/length. Replace the length check with totalLen > remaining where totalLen = optParamHdrSize + op.length.
  • getOptionalParametersLength(): Add early return when headerLen < sizeof(bgp_open_message). Refactored from nested if to early-return style.
  • setOptionalParameters(): Add early return false when headerLen < sizeof(bgp_open_message).
  • optional_parameter(): Zero-initialize with std::memset(this, 0, sizeof(*this)), resolving the existing FIXME.

Tests

Added BgpOpenMalformedOptionalParamsTest in Tests/Packet++Test/Tests/BgpTests.cpp with 5 sub-cases, each backed by a dedicated .dat fixture file:

Sub-test Scenario Expected result
1 headerLen (25) < sizeof(bgp_open_message) (29) 0 params, getOptionalParametersLength() = 0, setOptionalParameters() = false
2 optionalParameterLength = 5, only 1 byte of data available 0 params (truncated header detected)
3 optionalParameterLength = 5, first param claims length = 16 (OOB) 0 params (out-of-bounds length detected)
4 Valid first param (type=2, length=0) + truncated second param 1 param successfully parsed
5 optional_parameter default constructor All fields zeroed

@alacrity-aya alacrity-aya requested a review from seladb as a code owner May 18, 2026 10:37
@alacrity-aya alacrity-aya force-pushed the fix/bgp-open-optional-params-heap-overflow branch from 13b1eb8 to c939086 Compare May 18, 2026 10:47
…s() (seladb#1865)

Multiple out-of-bounds read vulnerabilities existed when parsing malformed
BGP OPEN messages with truncated or crafted optional parameters:

- dataPtr[0]/dataPtr[1] (type/length fields) could be read past the buffer
  when fewer than 2 bytes remained in the optional parameters region
- The individual parameter length check did not account for the 2-byte
  type+length header, allowing totalLen to exceed remaining bytes
- No bounds check on getHeaderLen() vs sizeof(bgp_open_message) in
  getOptionalParameters(), getOptionalParametersLength(), and
  setOptionalParameters()

Fixes:
- Add early return when headerLen < sizeof(bgp_open_message) in all three
  methods
- Check remaining < optParamHdrSize before reading type and length bytes
- Fix totalLen check to include the 2-byte parameter header
- Use std::min for cleaner clamping of optionalParamsLen
- Zero-initialize optional_parameter default constructor via std::memset

Tests:
- Add BgpOpenMalformedOptionalParamsTest covering:
  - BGP OPEN with header shorter than bgp_open_message struct
  - Truncated optional parameter header (< 2 bytes remaining)
  - Individual parameter length exceeding available buffer
  - Partial parse: valid first param + truncated second param
  - optional_parameter default constructor zero-initialization
@alacrity-aya alacrity-aya force-pushed the fix/bgp-open-optional-params-heap-overflow branch from c939086 to 3d2cd21 Compare May 18, 2026 11:02
@codecov

codecov Bot commented May 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.70130% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 82.72%. Comparing base (af83983) to head (60e89d9).

Files with missing lines Patch % Lines
Packet++/src/BgpLayer.cpp 96.87% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #2143      +/-   ##
==========================================
+ Coverage   82.70%   82.72%   +0.02%     
==========================================
  Files         332      332              
  Lines       59753    59800      +47     
  Branches    12288    12262      -26     
==========================================
+ Hits        49418    49469      +51     
- Misses       8932     8966      +34     
+ Partials     1403     1365      -38     
Flag Coverage Δ
23.11.6 7.29% <0.00%> (-0.05%) ⬇️
24.11.5 7.29% <0.00%> (-0.05%) ⬇️
25.11.1 7.27% <0.00%> (-0.06%) ⬇️
alpine320 76.85% <95.34%> (+0.04%) ⬆️
fedora42 76.44% <95.23%> (+0.04%) ⬆️
macos-14 82.26% <96.00%> (+0.04%) ⬆️
macos-15 82.25% <96.00%> (+0.03%) ⬆️
mingw32 71.11% <81.81%> (+0.01%) ⬆️
mingw64 71.09% <81.81%> (+0.15%) ⬆️
npcap ?
rhel94 76.25% <95.34%> (+0.01%) ⬆️
ubuntu2204 76.26% <95.34%> (+0.01%) ⬆️
ubuntu2204-icpx 59.40% <79.41%> (+0.05%) ⬆️
ubuntu2404 76.57% <95.34%> (+0.06%) ⬆️
ubuntu2404-arm64 76.54% <94.87%> (+0.02%) ⬆️
ubuntu2604 76.47% <95.23%> (+0.01%) ⬆️
unittest 82.72% <98.70%> (+0.02%) ⬆️
windows-2022 85.80% <96.87%> (+0.14%) ⬆️
windows-2025 85.83% <96.87%> (+0.14%) ⬆️
winpcap 85.83% <96.87%> (-0.06%) ⬇️
xdp 53.15% <95.34%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@alacrity-aya

alacrity-aya commented May 19, 2026

Copy link
Copy Markdown
Contributor Author

The original PoC from #1865 triggers a separate heap-buffer-overflow in TelnetLayer after the BGP fix is applied.

For more details, see TelnetLayer Heap-Buffer-Overflow

@Dimi1010 Dimi1010 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. One minor comment.

Comment thread Packet++/header/BgpLayer.h Outdated
Comment on lines +158 to +160
{
std::memset(this, 0, sizeof(*this));
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Maybe we can do it with default member initialization instead of memset?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update the code when I have some free time tomorrow

@alacrity-aya

Copy link
Copy Markdown
Contributor Author

Updated as suggested, PTAL. @Dimi1010

Comment thread Packet++/header/BgpLayer.h
Comment thread Packet++/src/BgpLayer.cpp Outdated
Comment thread Packet++/src/BgpLayer.cpp Outdated
Comment thread Packet++/src/BgpLayer.cpp Outdated
Comment thread Tests/Packet++Test/Tests/BgpTests.cpp Outdated
- Add static isDataValid() to BgpOpenMessageLayer, mirroring the
  pattern used by BgpUpdateMessageLayer. Rejects OPEN messages whose
  length field is smaller than sizeof(bgp_open_message).
- Use isDataValid() in parseBgpLayer() so malformed OPEN messages
  are rejected at parse time instead of producing a half-formed layer.
- Replace duplicated headerLen < sizeof(bgp_open_message) guards in
  getOptionalParameters(), getOptionalParametersLength(), and
  setOptionalParameters() with isDataValid().
- Add Doxygen comment to optional_parameter default constructor.
- Add explanatory comment for (std::min) parentheses.
- Update BgpOpenMalformedOptionalParamsTest: Test 1 now expects
  nullptr (layer rejected at parse time); use auto* for layer locals.
@alacrity-aya alacrity-aya requested a review from seladb May 26, 2026 06:42
Comment thread Packet++/header/BgpLayer.h Outdated
Comment thread Packet++/src/BgpLayer.cpp Outdated
Comment thread Packet++/src/BgpLayer.cpp Outdated
Comment thread Packet++/src/BgpLayer.cpp Outdated
Comment thread Packet++/src/BgpLayer.cpp Outdated
Comment thread Packet++/src/BgpLayer.cpp Outdated
Add pcap versions of newly added malformed BGP OPEN .dat fixtures so they can be inspected directly in Wireshark.
- Remove redundant OPEN layer validity guards in optional-parameter helpers
- Keep optional-parameter parsing bounds checks unchanged
- Keep <algorithm> include in BgpLayer.cpp and remove unnecessary header include
The early return for optionalParameterLength == 0 is unnecessary because
the subsequent while loop naturally handles this case (loop condition
byteCount < optionalParamsLen will be false when optionalParamsLen is 0).
@alacrity-aya alacrity-aya requested a review from seladb May 27, 2026 03:45

@seladb seladb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see 3 nit comments, otherwise LGTM

Comment thread Packet++/src/BgpLayer.cpp
Comment thread Packet++/src/BgpLayer.cpp Outdated
Comment thread Tests/Packet++Test/PacketExamples/Bgp_open_malformed_opt_param_len.pcap Outdated
- Add braces around early return in isDataValid
- Move standard library include after project includes
- Replace individual malformed BGP OPEN pcap fixtures with one combined pcap
@alacrity-aya alacrity-aya requested a review from seladb May 27, 2026 08:06
@seladb seladb merged commit 6ba29cf into seladb:dev May 27, 2026
42 checks passed
@seladb

seladb commented May 27, 2026

Copy link
Copy Markdown
Owner

Thank you @alacrity-aya for working on this, much appreciated! 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants