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");
}