Commit a237180b2d for frr
commit a237180b2d44ae1d4b0ea10b760a3772c7d32161
Author: Alejandro Pacheco <alepacheco99@gmail.com>
Date: Thu Oct 15 10:06:00 2026 +0200
ospfd: reject Router-LSAs with bytes after their link blocks
ospf_router_lsa_links_examin() walks the link blocks of a received
Router-LSA while at least a basic 12-byte block is left, and checks that
the number of blocks matches the declared link count. It does not check
that the blocks fill the LSA: 4 or 8 bytes after the last block are
accepted. The Router-LSA link walks in ospfd (ospf_spf_next(),
ospf_spf_process_stubs(), ospf_get_next_link(), TI-LFA P-space) run while
they are before the end of the LSA, so they then read
a whole link block where only those few bytes are left.
Reject a Router-LSA whose link blocks do not fill its body exactly, so
the link walks only ever see whole blocks.
Add tests/ospfd/test_ospf_lsa_examin for the Router-LSA length checks.
Signed-off-by: Alejandro Pacheco <alepacheco99@gmail.com>
diff --git a/ospfd/ospf_packet.c b/ospfd/ospf_packet.c
index 63ebf0902f..f42b75e860 100644
--- a/ospfd/ospf_packet.c
+++ b/ospfd/ospf_packet.c
@@ -3015,6 +3015,12 @@ static unsigned ospf_router_lsa_links_examin(struct router_lsa_link *link,
linkbytes -= thislinklen;
counted_links++;
}
+ if (linkbytes) {
+ if (IS_DEBUG_OSPF_PACKET(0, RECV))
+ zlog_debug("%s: %u bytes after link block #%u", __func__,
+ linkbytes, counted_links);
+ return MSG_NG;
+ }
if (counted_links != num_links) {
if (IS_DEBUG_OSPF_PACKET(0, RECV))
zlog_debug("%s: %u link blocks declared, %u present",
diff --git a/tests/ospfd/.gitignore b/tests/ospfd/.gitignore
index d2aaddcc07..e764237210 100644
--- a/tests/ospfd/.gitignore
+++ b/tests/ospfd/.gitignore
@@ -1,5 +1,6 @@
/*_afl/*
test_ospf_adj_pacing
+test_ospf_lsa_examin
test_ospf_spf
test_ospf_opaque_show
core
diff --git a/tests/ospfd/subdir.am b/tests/ospfd/subdir.am
index 48afe67f7b..c37183ae29 100644
--- a/tests/ospfd/subdir.am
+++ b/tests/ospfd/subdir.am
@@ -44,3 +44,13 @@ EXTRA_DIST += \
tests/ospfd/test_ospf_asbr_summary.py \
tests/ospfd/test_ospf_asbr_summary.refout \
# end
+
+
+if OSPFD
+check_PROGRAMS += tests/ospfd/test_ospf_lsa_examin
+endif
+tests_ospfd_test_ospf_lsa_examin_CFLAGS = $(TESTS_CFLAGS)
+tests_ospfd_test_ospf_lsa_examin_CPPFLAGS = $(TESTS_CPPFLAGS)
+tests_ospfd_test_ospf_lsa_examin_LDADD = $(OSPFD_TEST_LDADD)
+tests_ospfd_test_ospf_lsa_examin_SOURCES = tests/ospfd/test_ospf_lsa_examin.c
+EXTRA_DIST += tests/ospfd/test_ospf_lsa_examin.py
diff --git a/tests/ospfd/test_ospf_lsa_examin.c b/tests/ospfd/test_ospf_lsa_examin.c
new file mode 100644
index 0000000000..16cee261a5
--- /dev/null
+++ b/tests/ospfd/test_ospf_lsa_examin.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Router-LSA length checks: the link blocks of a received Router-LSA must
+ * fill its body exactly.
+ */
+
+#include "ospfd/ospf_packet.c"
+
+struct event_loop *master;
+struct zebra_privs_t ospfd_privs;
+
+#define RLSA_MAX_LEN \
+ (OSPF_LSA_HEADER_SIZE + OSPF_ROUTER_LSA_MIN_SIZE + 3 * OSPF_ROUTER_LSA_LINK_SIZE + \
+ OSPF_ROUTER_LSA_TOS_SIZE)
+
+static int failed;
+
+/* A Router-LSA with nlinks link blocks declaring declared links, the first
+ * block with tos TOS metrics, its length extra bytes longer.
+ */
+static uint16_t router_lsa(uint8_t *buf, uint16_t nlinks, uint16_t declared, uint8_t tos, int extra)
+{
+ struct router_lsa *rlsa = (struct router_lsa *)buf;
+ uint16_t len = OSPF_LSA_HEADER_SIZE + OSPF_ROUTER_LSA_MIN_SIZE +
+ nlinks * OSPF_ROUTER_LSA_LINK_SIZE + tos * OSPF_ROUTER_LSA_TOS_SIZE + extra;
+
+ memset(buf, 0, RLSA_MAX_LEN);
+ rlsa->header.type = OSPF_ROUTER_LSA;
+ rlsa->header.length = htons(len);
+ rlsa->links = htons(declared);
+ rlsa->link[0].tos = tos;
+ return len;
+}
+
+static void check(const char *name, uint16_t nlinks, uint16_t declared, uint8_t tos, int extra,
+ unsigned int expect)
+{
+ uint8_t buf[RLSA_MAX_LEN] __attribute__((aligned(4)));
+ uint16_t len = router_lsa(buf, nlinks, declared, tos, extra);
+ unsigned int ret = ospf_lsa_examin((struct lsa_header *)buf, len, 0);
+
+ printf("%s: %s\n", name, ret == expect ? "OK" : "failed!");
+ if (ret != expect)
+ failed++;
+}
+
+int main(void)
+{
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ check("router-lsa: accept 2 links", 2, 2, 0, 0, MSG_OK);
+ check("router-lsa: accept no links", 0, 0, 0, 0, MSG_OK);
+ check("router-lsa: accept 2 links, one with a TOS metric", 2, 2, 1, 0, MSG_OK);
+ check("router-lsa: reject 4 bytes after the links", 2, 2, 0, 4, MSG_NG);
+ check("router-lsa: reject 8 bytes after the links", 2, 2, 0, 8, MSG_NG);
+ check("router-lsa: reject 4 bytes and no links", 0, 0, 0, 4, MSG_NG);
+ check("router-lsa: reject 4 bytes after a link with a TOS metric", 1, 1, 1, 4, MSG_NG);
+ check("router-lsa: reject a truncated TOS metric", 1, 1, 1, -4, MSG_NG);
+ check("router-lsa: reject a missing link", 1, 2, 0, 0, MSG_NG);
+ check("router-lsa: reject a header only", 0, 0, 0, -4, MSG_NG);
+
+ return failed;
+}
diff --git a/tests/ospfd/test_ospf_lsa_examin.py b/tests/ospfd/test_ospf_lsa_examin.py
new file mode 100644
index 0000000000..8b5d89f708
--- /dev/null
+++ b/tests/ospfd/test_ospf_lsa_examin.py
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+import frrtest
+
+
+class TestOspfLsaExamin(frrtest.TestMultiOut):
+ program = "./test_ospf_lsa_examin"
+
+
+TestOspfLsaExamin.okfail("router-lsa: accept 2 links:")
+TestOspfLsaExamin.okfail("router-lsa: accept no links:")
+TestOspfLsaExamin.okfail("router-lsa: accept 2 links, one with a TOS metric:")
+TestOspfLsaExamin.okfail("router-lsa: reject 4 bytes after the links:")
+TestOspfLsaExamin.okfail("router-lsa: reject 8 bytes after the links:")
+TestOspfLsaExamin.okfail("router-lsa: reject 4 bytes and no links:")
+TestOspfLsaExamin.okfail("router-lsa: reject 4 bytes after a link with a TOS metric:")
+TestOspfLsaExamin.okfail("router-lsa: reject a truncated TOS metric:")
+TestOspfLsaExamin.okfail("router-lsa: reject a missing link:")
+TestOspfLsaExamin.okfail("router-lsa: reject a header only:")