Skip to content

bgpd: Fix a couple of issues in BGP-LS NLRI encoding/decoding#21092

Merged
riw777 merged 8 commits intoFRRouting:masterfrom
cscarpitta:fix_bgp_ls_encoding_decoding
Mar 24, 2026
Merged

bgpd: Fix a couple of issues in BGP-LS NLRI encoding/decoding#21092
riw777 merged 8 commits intoFRRouting:masterfrom
cscarpitta:fix_bgp_ls_encoding_decoding

Conversation

@cscarpitta
Copy link
Copy Markdown
Contributor

This PR solves a couple of issues in BGP-LS NLRI encoding/decoding.

See individual commits.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 11, 2026

Greptile Summary

This PR fixes several correctness and safety issues in the BGP-LS NLRI encoding and decoding pipeline. Changes span attribute encoding (bgp_attr.c), NLRI parsing (bgp_ls.c), and the low-level encode/decode routines (bgp_ls_nlri.c/bgp_ls_nlri.h).

Key fixes included:

  • Extended-length attribute header (bgp_attr.c): The BGP-LS path attribute was being encoded with a 1-byte length field (stream_putc) but without setting BGP_ATTR_FLAG_EXTLEN. RFC 9552 requires a 2-byte length for this attribute. This mismatch would cause peer implementations to misparse the attribute length, breaking BGP-LS sessions. The fix sets the EXTLEN flag and uses stream_putw/stream_putw_at throughout, and adds a UINT16_MAX overflow guard.

  • IGP Router ID buffer overflow (bgp_ls_nlri.h): igp_router_id[] was sized at 8 bytes, but BGP_LS_IGP_ROUTER_ID_MAX_SIZE (used as the accepted maximum during decoding) is 16 bytes (IPv6). Receiving a Direct/Static IPv6 Router ID would overflow into adjacent struct fields. The buffer is now correctly sized to BGP_LS_IGP_ROUTER_ID_MAX_SIZE.

  • Double ntohl in Extended Admin Group decoding (bgp_ls_nlri.c): The old code applied ntohl() on top of stream_getl(), which already performs big-endian-to-host conversion internally. On little-endian systems (x86), this double-inversion silently produced byte-reversed admin group bitmaps. The redundant ntohl() is removed; the pattern is now consistent with other callers of admin_group_bulk_set in the codebase (e.g., lib/zclient.c, lib/link_state.c).

  • Stream writability guards (bgp_ls_nlri.c): STREAM_WRITEABLE checks are added before every write to the stream in encode paths, allowing functions to return -1 cleanly rather than silently truncating or corrupting output.

  • Duplicate TLV detection (bgp_ls_nlri.c): A wide set of node/link/prefix descriptor TLVs and attribute TLVs now reject duplicates with a warning, tightening protocol compliance and preventing partial overwrites of fields.

  • NLRI temporary allocation (bgp_ls.c): The per-iteration stack-allocated struct bgp_ls_nlri nlri is replaced with a heap-allocated bgp_ls_nlri_alloc() pointer, which is properly freed after use. This change is safe because bgp_ls_nlri_get() always creates an independent deep copy when inserting into the hash, so ls_entry and the local nlri are distinct allocations.

Confidence Score: 5/5

  • This PR is safe to merge; it fixes real bugs with no regressions introduced.
  • All changes are targeted correctness fixes backed by the RFC and codebase conventions. The EXTLEN flag and 2-byte length fix is a protocol-level correctness requirement. The igp_router_id buffer expansion closes a latent stack/heap overflow. The ntohl removal eliminates a byte-order inversion on little-endian hosts, consistent with how every other admin_group_bulk_set caller in the tree behaves. Memory management in bgp_ls.c is correct: the temporary heap nlri is always freed, and ls_entry is an independent deep copy. The stream writability guards and duplicate TLV checks add defensive depth without changing correct-input behavior. No pre-existing interfaces are broken.
  • No files require special attention.

Important Files Changed

Filename Overview
bgpd/bgp_attr.c Fixes BGP-LS attribute header to use EXTLEN flag with 2-byte length field per RFC 9552; adds UINT16_MAX overflow guard and rolls back stream on error
bgpd/bgp_ls.c Changes NLRI decoding from stack-allocated struct to heap-allocated pointer with proper allocation/free lifecycle; adds NULL guards for peer/BGP/ls_info
bgpd/bgp_ls_nlri.c Adds stream writability checks before every write, comprehensive duplicate TLV detection, correct removal of double ntohl conversion in parse_ext_admin_group, and goto-error cleanup for link/prefix descriptor parsing
bgpd/bgp_ls_nlri.h Extends igp_router_id[] from 8 to BGP_LS_IGP_ROUTER_ID_MAX_SIZE (16) bytes to support IPv6 router IDs; adds BGP_LS_MAX_EXT_ADMIN_GROUPS constant

Sequence Diagram

sequenceDiagram
    participant Peer as BGP Peer
    participant Parse as bgp_nlri_parse_ls
    participant Decode as bgp_ls_decode_nlri
    participant Hash as nlri_hash
    participant RIB as BGP RIB

    Peer->>Parse: packet MP_REACH or UNREACH NLRI
    Note over Parse: Guard: peer/bgp/ls_info/packet not NULL

    loop for each NLRI in stream
        Parse->>Decode: bgp_ls_nlri_alloc() then decode into heap nlri
        Decode->>Decode: Parse TLVs with duplicate detection
        Decode->>Decode: STREAM_WRITEABLE and STREAM_READABLE checks
        Decode-->>Parse: decoded nlri or -1 on error

        alt decode failed
            Parse->>Parse: bgp_ls_nlri_free(nlri)
            Parse-->>Peer: BGP_NLRI_PARSE_ERROR
        else decode OK
            Parse->>Hash: bgp_ls_nlri_get(nlri)
            Note over Hash: deep-copy nlri into hash if new, refcnt incremented
            Hash-->>Parse: ls_entry which is hash-owned copy
            Parse->>RIB: bgp_update or bgp_withdraw
            Parse->>Parse: bgp_ls_nlri_free(nlri) to free temp alloc
        end
    end

    Parse-->>Peer: BGP_NLRI_PARSE_OK
Loading

Last reviewed commit: 03dbdfd

@cscarpitta cscarpitta force-pushed the fix_bgp_ls_encoding_decoding branch from 65f6b2d to f81a315 Compare March 11, 2026 15:22
@cscarpitta
Copy link
Copy Markdown
Contributor Author

@greptile review

@cscarpitta cscarpitta force-pushed the fix_bgp_ls_encoding_decoding branch from f81a315 to 8e3b5fc Compare March 11, 2026 17:37
@cscarpitta
Copy link
Copy Markdown
Contributor Author

@greptile review

@cscarpitta
Copy link
Copy Markdown
Contributor Author

@greptile review

@cscarpitta cscarpitta force-pushed the fix_bgp_ls_encoding_decoding branch from 195d66d to 03dbdfd Compare March 13, 2026 08:51
@cscarpitta
Copy link
Copy Markdown
Contributor Author

@greptile review

@github-actions
Copy link
Copy Markdown

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@cscarpitta cscarpitta force-pushed the fix_bgp_ls_encoding_decoding branch from 03dbdfd to 04cbca5 Compare March 20, 2026 23:59
BGP receives BGP Update messages that can contain a BGP-LS NLRI, then
processes the TLVs carried in that NLRI. The NLRI may include duplicate
TLVs. Today, when a duplicate TLV is received, BGP-LS overwrites the
previous value with the new one.

RFC 9552 requires duplicate TLVs to be treated as a malformed NLRI.
Also, replacing an earlier TLV value with a new one without freeing
previously allocated dynamic memory can cause a memory leak.

Fix by checking whether the same TLV type appears more than once and
returning an error on duplicates. This propagates the parse failure to
the caller, which then treats the NLRI as malformed.

Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com>
Some encode paths wrote to the stream without first checking that enough
space was available.

Writing when the stream is full can trigger an assert or crash.

Fix by adding `STREAM_WRITEABLE()` checks before writing, and return -1
when space is insufficient.

Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com>
parse_ext_admin_group() only verified that the TLV length was a multiple
of 4, with no upper bound on the number of 32-bit words to decode.
A very large length could trigger excessive growth/work in
admin_group_bulk_set().

Define BGP_LS_MAX_EXT_ADMIN_GROUP_WORDS in bgp_ls_nlri.h and reject
Extended Admin Group TLVs that exceed this limit during decode.

Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com>
`parse_ext_admin_group()` applied `ntohl()` to the value returned by
`stream_getl()`. `stream_getl()` already returns the 32-bit value in host
byte order, so this caused a second conversion.

On little-endian systems, the extra conversion byte-swapped the value
again and corrupted decoded Extended Admin Group words.

Fix by removing the redundant conversion and passing `stream_getl()`
output directly to `admin_group_bulk_set()`.

Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com>
In `bgp_nlri_parse_ls()`, `bgp_ls_nlri` was declared on the stack and then
filled by `bgp_ls_decode_nlri()` from the input stream.

`bgp_ls_decode_nlri()` can allocate dynamic fields inside the NLRI object.
Because the parsing loop reused the same stack object on each iteration,
those dynamic allocations were leaked across iterations.

Fix by moving the NLRI object to heap allocation and using the BGP-LS NLRI
helpers for lifecycle management: allocate with `bgp_ls_nlri_alloc()` and
free with `bgp_ls_nlri_free()` on both success and error paths.

Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com>
bgp_ls_decode_link_descriptor() and bgp_ls_decode_prefix_descriptor()
can allocate desc->mt_id when parsing MT-ID TLVs.

If a later TLV in the same descriptor fails validation, the functions
returned -1 without releasing that allocation, leaking memory on error
unwind.

Fix by adding centralized error cleanup paths in both descriptor parsers
that free desc->mt_id before returning -1.

Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com>
bgp_packet_ls_attribute() encoded BGP-LS attributes with a 1-byte
length field, which can encode up to 255 bytes of BGP-LS attribute data.

BGP-LS attributes often exceed 255 bytes when carrying multiple
node/link/prefix TLVs, so a 1-byte length is not sufficient.

Fix by setting BGP_ATTR_FLAG_EXTLEN for BGP-LS attributes and
encoding the attribute length as 2 bytes. Add a bounds check to
ensure the encoded attribute length fits in the extended-length field.

Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com>
`bgp_nlri_parse_ls()` dereferenced `peer->bgp->ls_info` without checking
that BGP-LS state was initialized.

Add defensive checks for peer, peer->bgp, peer->bgp->ls_info, and
packet before parsing, and return BGP_NLRI_PARSE_ERROR on invalid state.

Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com>
@cscarpitta cscarpitta force-pushed the fix_bgp_ls_encoding_decoding branch from 04cbca5 to 2e533d3 Compare March 21, 2026 00:04
Copy link
Copy Markdown
Member

@riw777 riw777 left a comment

Choose a reason for hiding this comment

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

looks good

@riw777 riw777 merged commit 48ad1ad into FRRouting:master Mar 24, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants