From 142940118e0a5a329a88154620bb6d08ba735d62 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Mon, 29 Sep 2025 17:09:09 +0800 Subject: [PATCH] Avoids circular dependency in profile groups Adds cycle detection to prevent infinite loops when evaluating profile groups. This ensures that profile group configurations don't result in stack overflow errors when groups reference each other, directly or indirectly. --- v2rayN/ServiceLib/Models/ProfileItem.cs | 36 +++++++++++++++++++ .../Singbox/SingboxOutboundService.cs | 9 +++-- .../CoreConfig/V2ray/V2rayOutboundService.cs | 7 ++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/v2rayN/ServiceLib/Models/ProfileItem.cs b/v2rayN/ServiceLib/Models/ProfileItem.cs index da34600b..8f883535 100644 --- a/v2rayN/ServiceLib/Models/ProfileItem.cs +++ b/v2rayN/ServiceLib/Models/ProfileItem.cs @@ -109,6 +109,42 @@ public class ProfileItem : ReactiveObject return true; } + public async Task HasCycle(HashSet visited, HashSet stack) + { + if (ConfigType < EConfigType.Group) + return false; + + if (stack.Contains(IndexId)) + return true; + + if (visited.Contains(IndexId)) + return false; + + visited.Add(IndexId); + stack.Add(IndexId); + + if (ProfileGroupItemManager.Instance.TryGet(IndexId, out var group) + && !group.ChildItems.IsNullOrEmpty()) + { + var childProfiles = (await Task.WhenAll( + Utils.String2List(group.ChildItems) + .Where(p => !p.IsNullOrEmpty()) + .Select(AppManager.Instance.GetProfileItem) + )) + .Where(p => p != null) + .ToList(); + + foreach (var child in childProfiles) + { + if (await child.HasCycle(visited, stack)) + return true; + } + } + + stack.Remove(IndexId); + return false; + } + #endregion function [PrimaryKey] diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index bd03b4c8..8bc93e71 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -217,9 +217,15 @@ public partial class CoreConfigSingboxService { return -1; } + + var hasCycle = await node.HasCycle(new HashSet(), new HashSet()); + if (hasCycle) + { + return -1; + } + // remove custom nodes // remove group nodes for proxy chain - // avoid self-reference var childProfiles = (await Task.WhenAll( Utils.String2List(profileGroupItem.ChildItems) .Where(p => !p.IsNullOrEmpty()) @@ -230,7 +236,6 @@ public partial class CoreConfigSingboxService && p.IsValid() && p.ConfigType != EConfigType.Custom && (node.ConfigType == EConfigType.PolicyGroup || p.ConfigType < EConfigType.Group) - && p.IndexId != node.IndexId ) .ToList(); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs index b250e01d..3d6597ae 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs @@ -493,6 +493,13 @@ public partial class CoreConfigV2rayService { return -1; } + + var hasCycle = await node.HasCycle(new HashSet(), new HashSet()); + if (hasCycle) + { + return -1; + } + // remove custom nodes // remove group nodes for proxy chain var childProfiles = (await Task.WhenAll(