Commit 59b281bdd8 for asterisk.org

commit 59b281bdd875124877d9eea8b7d5c8ff36a5b931
Author: Alexei Gradinari <alex2grad@gmail.com>
Date:   Wed Jan 7 15:39:05 2026 -0500

    res_pjsip_pubsub: Fix ao2 reference leak of subscription tree in ast_sip_subscription

    allocate_subscription() increments the ao2 reference count of the subscription tree,
    but the reference was not consistently released during subscription destruction,
    resulting in leaked sip_subscription_tree objects.

    This patch makes destroy_subscription() responsible for releasing sub->tree,
    removes ad-hoc cleanup in error paths,
    and guards tree cleanup to ensure refcount symmetry and correct ownership.

    Fixes: #1703

diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index 6ae0e3ed7b..644b54238f 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -1446,6 +1446,12 @@ static void destroy_subscription(struct ast_sip_subscription *sub)
 	ao2_cleanup(sub->datastores);
 	ast_json_unref(sub->persistence_data);
 	ast_free(sub->display_name);
+	if (sub->tree) {
+		/* Clear tree before cleanup to avoid re-entrant destruction */
+		struct sip_subscription_tree *tree = sub->tree;
+		sub->tree=NULL;
+		ao2_cleanup(tree);
+	}
 	ast_free(sub);
 }

@@ -1561,10 +1567,7 @@ static struct ast_sip_subscription *create_virtual_subscriptions(const struct as
 		if (AST_VECTOR_APPEND(&sub->children, child)) {
 			ast_debug(1, "Child subscription to resource %s could not be appended\n",
 					child_node->resource);
-			destroy_subscription(child);
-			/* Have to release tree here too because a ref was added
-			 * to child that destroy_subscription() doesn't release. */
-			ao2_cleanup(tree);
+			destroy_subscriptions(child);
 		}
 	}

@@ -1637,7 +1640,12 @@ void ast_sip_subscription_destroy(struct ast_sip_subscription *sub)
 {
 	ast_debug(3, "Removing subscription %p '%s->%s' reference to subscription tree %p\n",
 		sub, ast_sorcery_object_get_id(sub->tree->endpoint), sub->resource, sub->tree);
-	ao2_cleanup(sub->tree);
+	if (sub->tree) {
+		/* Clear tree before cleanup to avoid re-entrant destruction */
+		struct sip_subscription_tree *tree = sub->tree;
+		sub->tree = NULL;
+		ao2_cleanup(tree);
+	}
 }

 static void subscription_setup_dialog(struct sip_subscription_tree *sub_tree, pjsip_dialog *dlg)