Hello BIRD maintainers,

I would like to report a possible RFC 7607 compliance issue in BIRD's BGP UPDATE validation: routes whose AS_PATH contains AS number 0 appear to be accepted into the BGP table instead of being treated as malformed.

Summary

BIRD appears not to reject AS 0 when it appears inside the AS_PATH path attribute.

I tested this with an eBGP session where the peer sent a valid UPDATE whose AS_PATH was:

65002 0

BIRD accepted the route and displayed the AS_PATH with the reserved AS 0 still present.

RFC 7607 Section 2 states that a BGP speaker must not originate or propagate a route with AS number 0 in AS_PATH, and that an UPDATE containing AS number 0 in AS_PATH must be considered malformed and handled according to RFC 7606.

For a malformed AS_PATH attribute with parseable NLRI, RFC 7606 handling should be treat-as-withdraw: the route should not be installed, while the BGP session should remain established.

Affected Version

I reproduced this on:

BIRD version 2.19.0+branch.master.880200b1f94c
commit 880200b1

Observed Behavior

After establishing an eBGP session, the peer sent an UPDATE for 192.0.2.0/24 with:

ORIGIN: IGP
AS_PATH: 65002 0
NEXT_HOP: 10.0.0.2
NLRI: 192.0.2.0/24

BIRD accepted the UPDATE. birdc show route 192.0.2.0/24 all showed:

Table master4:
192.0.2.0/24         unreachable [as0_test ... from 10.100.0.2] * (100) [AS0i]
        Type: BGP univ
        BGP.origin: IGP
        BGP.as_path: 65002 0
        BGP.next_hop: 10.0.0.2
        BGP.local_pref: 100

The route was marked unreachable because of next-hop resolution in the local test setup, but the important point is that the route was accepted into the BGP table with:

BGP.as_path: 65002 0

I did not observe an error, warning, or treat-as-withdraw behavior for the reserved AS 0.

The BIRD log showed the UPDATE being accepted:

as0_test: BGP session established
as0_test: Got UPDATE
as0_test.ipv4 > added [best] 192.0.2.0/24 ...

Expected Behavior

BIRD should detect AS 0 in AS_PATH and treat the UPDATE as malformed.

Since the UPDATE contains reachable NLRI and the NLRI is parseable, the expected behavior is:

Source-Level Analysis

The AS_PATH decoder validates AS_PATH structure but does not appear to validate individual AS numbers against the reserved AS 0 value.

In proto/bgp/attrs.cbgp_decode_as_path() calls as_path_valid() and then stores the attribute:

if (!as_path_valid(data, len, as_length, as_sets, as_confed, err, sizeof(err)))
  WITHDRAW("Malformed AS_PATH attribute - %s", err);

...

bgp_set_attr_data(to, s->pool, BA_AS_PATH, flags, data, len);

The later post-decode checks in bgp_decode_attrs() cover mandatory attribute presence, local-AS loop detection, confederation loop detection, ORIGINATOR_ID loop detection, and CLUSTER_LIST loop detection, but I could not find a check for AS 0:

if (bgp_as_path_loopy(p, attrs, p->local_as))
  goto loop;

if ((p->public_as != p->local_as) && bgp_as_path_loopy(p, attrs, p->public_as))
  goto loop;

In nest/a-path.cas_path_valid() checks segment framing, segment type, whether AS_SET / AS_CONFED_* are allowed, and zero-length segments:

switch (type)
{
case AS_PATH_SET:
  if (!sets)
    BAD("AS_SET segment", type);
  break;

case AS_PATH_SEQUENCE:
  break;

case AS_PATH_CONFED_SEQUENCE:
  if (!confed)
    BAD("AS_CONFED_SEQUENCE segment", type);
  break;

case AS_PATH_CONFED_SET:
  if (!sets || !confed)
    BAD("AS_CONFED_SET segment", type);
  break;

default:
  BAD("unknown segment", type);
}

if (pos[1] == 0)
  BAD("zero-length segment", type);

I did not find an equivalent validation pass that iterates through the AS numbers in the path and rejects asn == 0.

Suggested Fix

Add AS 0 validation during AS_PATH parsing or immediately after AS_PATH decoding.

Conceptually:

/* RFC 7607: AS 0 is reserved and must not appear in AS_PATH */
if (as_path_contains_as_zero(...))
  WITHDRAW("Malformed AS_PATH attribute - AS 0 is reserved");

A similar validation may also be relevant for AS4_PATH, AGGREGATOR, and AS4_AGGREGATOR, since RFC 7607 also covers those attributes. However, this report only describes the AS_PATH case that I reproduced dynamically.

Impact

Accepting AS 0 in AS_PATH can cause interoperability and policy issues:

This does not require tearing down the BGP session. Treat-as-withdraw should be sufficient and would match the usual RFC 7606 approach for malformed AS_PATH attributes.

Please let me know if you would like a minimal reproducer or packet capture.

Best regards,
Xinzhe Liu