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:")