Commit 48d74c77c9 for frr

commit 48d74c77c9eda9680b7785a44d23a04231dfc160
Author: Donald Sharp <sharpd@nvidia.com>
Date:   Wed Sep 23 09:35:46 2026 -0400

    lib, tests: Fix double free of an edge and pathd crash fix

    TED edges live both on the edge tree and the vertex lists: outgoing_edges
    and incoming_edges.  The ls_vertex_del() frees every outgoing edge,
    which unlinks it from edge->source and edge->destination only.  The
    incoming_edges is left untouched.  When that vertex is destroyed
    the stale list entry is freed again causing a crash.

    Fix the double crash by freeing the edge from all lists when it is
    originally freed.  Introduce a change to `make check` such that
    it will test for this condition as well and make sure it does not
    continue to happen.

    Signed-off-by: Donald Sharp <sharpd@nvidia.com>

diff --git a/lib/link_state.c b/lib/link_state.c
index 33e6c5aa7a..1cc65b6281 100644
--- a/lib/link_state.c
+++ b/lib/link_state.c
@@ -690,8 +690,13 @@ void ls_vertex_clean(struct ls_ted *ted, struct ls_vertex *vertex,
  * A temporary vertex that corresponds to the source of this Edge i.e. the
  * advertised router, is created if not found in the Data Base. If a Edge that
  * corresponds to the reverse path is found, the Edge is attached to the
- * destination vertex as destination and reverse Edge is attached to the source
- * vertex as source.
+ * destination vertex as destination. The reverse Edge is attached to this
+ * vertex only while it has no destination of its own.
+ *
+ * The reverse edge is looked up by its local address, so more than one edge
+ * can name it (a broadcast LAN, for example). Moving a reverse edge that is
+ * already linked leaves it on the previous incoming list, and TED teardown
+ * then frees that edge twice.
  *
  * @param ted	Link State Data Base
  * @param edge	Link State Edge to be attached
@@ -717,15 +722,22 @@ static void ls_edge_connect_to(struct ls_ted *ted, struct ls_edge *edge)

 	/* Then search if there is a reverse Edge */
 	dst = ls_find_edge_by_destination(ted, edge->attributes);
-	/* attach the destination edge to the vertex */
-	if (dst) {
+	if (!dst)
+		return;
+
+	/* Keep an already linked reverse edge where its remote address put it. */
+	if (dst->destination == NULL) {
 		listnode_add_sort_nodup(vertex->incoming_edges, dst);
 		dst->destination = vertex;
-		/* and destination vertex to this edge */
-		vertex = dst->source;
-		listnode_add_sort_nodup(vertex->incoming_edges, edge);
-		edge->destination = vertex;
 	}
+
+	/* and destination vertex to this edge */
+	vertex = dst->source;
+	if (!vertex || edge->destination != NULL)
+		return;
+
+	listnode_add_sort_nodup(vertex->incoming_edges, edge);
+	edge->destination = vertex;
 }

 static struct ls_edge_key get_edge_key(struct ls_attributes *attr, bool dst)
diff --git a/tests/lib/test_link_state.c b/tests/lib/test_link_state.c
index 15d9072dc3..ce9a93ecff 100644
--- a/tests/lib/test_link_state.c
+++ b/tests/lib/test_link_state.c
@@ -83,8 +83,31 @@ static void test_edge_remote_endpoint_update(void)
 	assert(ted == NULL);
 }

+/*
+ * Two edges name the same remote address, so one reverse edge is claimed
+ * twice. Router ids are vertex keys: teardown walks 1, then 2, then 3.
+ * That order disconnects the reverse edge from its first destination, frees
+ * it with the second vertex, and then finds it still listed on the third.
+ * The old code aborts there. Teardown must free each edge once.
+ */
+static void test_shared_remote_does_not_double_free(void)
+{
+	struct ls_ted *ted;
+
+	ted = ls_ted_new(1, "link-state-test", 0);
+	assert(ted);
+
+	assert(ls_edge_add(ted, edge_attributes(1, "10.0.1.1", "10.0.1.2")));
+	assert(ls_edge_add(ted, edge_attributes(2, "10.0.1.2", "10.0.1.1")));
+	assert(ls_edge_add(ted, edge_attributes(3, "10.0.1.3", "10.0.1.2")));
+
+	ls_ted_del_all(&ted);
+	assert(ted == NULL);
+}
+
 int main(void)
 {
 	test_edge_remote_endpoint_update();
+	test_shared_remote_does_not_double_free();
 	printf("Link State database tests passed.\n");
 }