Use RegDeleteTree in RegistryKey.DeleteSubKeyTree (#82598)
authorHuo Yaoyuan <huoyaoyuan@hotmail.com>
Fri, 21 Jul 2023 09:12:59 +0000 (17:12 +0800)
committerGitHub <noreply@github.com>
Fri, 21 Jul 2023 09:12:59 +0000 (11:12 +0200)
* Use RegDeleteTree in RegistryKey.DeleteSubKeyTree

* Restore self delete behavior

* Call RegDeleteTree on subkey to simulate permission behavior.

* Adjust comment and add tests.

src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegDeleteTree.cs [new file with mode: 0644]
src/libraries/Microsoft.Win32.Registry/src/Microsoft.Win32.Registry.csproj
src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs
src/libraries/Microsoft.Win32.Registry/tests/RegistryKey/RegistryKey_DeleteSubKeyTree_str.cs

diff --git a/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegDeleteTree.cs b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegDeleteTree.cs
new file mode 100644 (file)
index 0000000..5228962
--- /dev/null
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if REGISTRY_ASSEMBLY
+using Microsoft.Win32.SafeHandles;
+#else
+using Internal.Win32.SafeHandles;
+#endif
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+    internal static partial class Advapi32
+    {
+        [LibraryImport(Libraries.Advapi32, EntryPoint = "RegDeleteTreeW", StringMarshalling = StringMarshalling.Utf16)]
+        internal static partial int RegDeleteTree(
+            SafeRegistryHandle hKey,
+            string lpSubKey);
+    }
+}
index 987e854..7cb3100 100644 (file)
@@ -28,6 +28,8 @@
              Link="Common\Interop\Windows\Interop.RegCreateKeyEx.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegDeleteKeyEx.cs"
              Link="Common\Interop\Windows\Interop.RegDeleteKeyEx.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegDeleteTree.cs"
+             Link="Common\Interop\Windows\Interop.RegDeleteTree.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegDeleteValue.cs"
              Link="Common\Interop\Windows\Interop.RegDeleteValue.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegEnumKeyEx.cs"
index ea8a409..f1600c8 100644 (file)
@@ -334,65 +334,36 @@ namespace Microsoft.Win32
 
             subkey = FixupName(subkey); // Fixup multiple slashes to a single slash
 
+            // If the key has values, it must be opened with KEY_SET_VALUE,
+            // or RegDeleteTree will fail with ERROR_ACCESS_DENIED.
+
             RegistryKey? key = InternalOpenSubKeyWithoutSecurityChecks(subkey, true);
             if (key != null)
             {
                 using (key)
                 {
-                    if (key.SubKeyCount > 0)
+                    int ret = Interop.Advapi32.RegDeleteTree(key._hkey, string.Empty);
+                    if (ret != 0)
                     {
-                        string[] keys = key.GetSubKeyNames();
-
-                        for (int i = 0; i < keys.Length; i++)
-                        {
-                            key.DeleteSubKeyTreeInternal(keys[i]);
-                        }
+                        Win32Error(ret, null);
                     }
-                }
 
-                DeleteSubKeyTreeCore(subkey);
-            }
-            else if (throwOnMissingSubKey)
-            {
-                throw new ArgumentException(SR.Arg_RegSubKeyAbsent);
-            }
-        }
+                    // RegDeleteTree doesn't self-delete when lpSubKey is empty.
+                    // Manually delete the key to restore old behavior.
 
-        /// <summary>
-        /// An internal version which does no security checks or argument checking.  Skipping the
-        /// security checks should give us a slight perf gain on large trees.
-        /// </summary>
-        private void DeleteSubKeyTreeInternal(string subkey)
-        {
-            RegistryKey? key = InternalOpenSubKeyWithoutSecurityChecks(subkey, true);
-            if (key != null)
-            {
-                using (key)
-                {
-                    if (key.SubKeyCount > 0)
+                    ret = Interop.Advapi32.RegDeleteKeyEx(key._hkey, string.Empty, (int)_regView, 0);
+                    if (ret != 0)
                     {
-                        string[] keys = key.GetSubKeyNames();
-                        for (int i = 0; i < keys.Length; i++)
-                        {
-                            key.DeleteSubKeyTreeInternal(keys[i]);
-                        }
+                        Win32Error(ret, null);
                     }
                 }
-
-                DeleteSubKeyTreeCore(subkey);
             }
             else
             {
-                throw new ArgumentException(SR.Arg_RegSubKeyAbsent);
-            }
-        }
-
-        private void DeleteSubKeyTreeCore(string subkey)
-        {
-            int ret = Interop.Advapi32.RegDeleteKeyEx(_hkey, subkey, (int)_regView, 0);
-            if (ret != 0)
-            {
-                Win32Error(ret, null);
+                if (throwOnMissingSubKey)
+                {
+                    throw new ArgumentException(SR.Arg_RegSubKeyAbsent);
+                }
             }
         }
 
index c8606e1..727087d 100644 (file)
@@ -3,7 +3,6 @@
 
 using System;
 using System.Linq;
-using System.Reflection;
 using Xunit;
 
 namespace Microsoft.Win32.RegistryTests
@@ -49,6 +48,38 @@ namespace Microsoft.Win32.RegistryTests
 
             Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
         }
+        
+        [Fact]
+        public void SelfDeleteWithValuesTest()
+        {
+            using (var rk = TestRegistryKey.CreateSubKey(TestRegistryKeyName))
+            {
+                rk.SetValue("VAL", "Dummy", RegistryValueKind.String);
+                rk.SetDefaultValue("Default");
+                using RegistryKey created = rk.CreateSubKey(TestRegistryKeyName);
+                created.SetValue("Value", 42, RegistryValueKind.DWord);
+                rk.DeleteSubKeyTree("");
+            }
+
+            Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
+        }
+        
+        [Fact]
+        public void SelfDeleteWithValuesTest_AnotherHandlePresent()
+        {
+            using (var rk = TestRegistryKey.CreateSubKey(TestRegistryKeyName))
+            {
+                rk.SetValue("VAL", "Dummy", RegistryValueKind.String);
+                rk.SetDefaultValue("Default");
+                using RegistryKey created = rk.CreateSubKey(TestRegistryKeyName);
+                created.SetValue("Value", 42, RegistryValueKind.DWord);
+
+                using var rk2 = TestRegistryKey.OpenSubKey(TestRegistryKeyName);
+                rk.DeleteSubKeyTree("");
+            }
+
+            Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
+        }
 
         [Fact]
         public void DeleteSubKeyTreeTest()
@@ -85,6 +116,37 @@ namespace Microsoft.Win32.RegistryTests
             TestRegistryKey.DeleteSubKeyTree(TestRegistryKeyName);
             Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
         }
+        
+        [Fact]
+        public void DeleteSubKeyTreeTest3()
+        {
+            // [] Add in multiple subkeys and then delete the root key
+            string[] subKeyNames = Enumerable.Range(1, 9).Select(x => "BLAH_" + x.ToString()).ToArray();
+
+            using (RegistryKey rk = TestRegistryKey.CreateSubKey(TestRegistryKeyName))
+            {
+                foreach (var subKeyName in subKeyNames)
+                {
+                    using RegistryKey rk2 = rk.CreateSubKey(subKeyName);
+                    Assert.NotNull(rk2);
+
+                    using RegistryKey rk3 = rk2.CreateSubKey("Test");
+                    Assert.NotNull(rk3);
+                }
+
+                Assert.Equal(subKeyNames, rk.GetSubKeyNames());
+
+                // Add multiple values to the key being deleted
+                foreach (int i in Enumerable.Range(1, 9))
+                {
+                    rk.SetValue("STRVAL_" + i, i.ToString(), RegistryValueKind.String);
+                    rk.SetValue("INTVAL_" + i, i, RegistryValueKind.DWord);
+                }
+            }
+
+            TestRegistryKey.DeleteSubKeyTree(TestRegistryKeyName);
+            Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
+        }
 
         [Theory]
         [MemberData(nameof(TestRegistrySubKeyNames))]