From 140472600c9a68301c7c0154636bd68929f9960f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 28 Feb 2023 17:26:57 +0800 Subject: [PATCH 1/4] Use RegDeleteTree in RegistryKey.DeleteSubKeyTree --- .../Windows/Advapi32/Interop.RegDeleteTree.cs | 20 +++++++ .../src/Microsoft.Win32.Registry.csproj | 2 + .../src/Microsoft/Win32/RegistryKey.cs | 57 ++----------------- 3 files changed, 27 insertions(+), 52 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegDeleteTree.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 index 00000000000000..52289627bc243d --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegDeleteTree.cs @@ -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); + } +} diff --git a/src/libraries/Microsoft.Win32.Registry/src/Microsoft.Win32.Registry.csproj b/src/libraries/Microsoft.Win32.Registry/src/Microsoft.Win32.Registry.csproj index 5920905308d2fc..438a4d0caf5926 100644 --- a/src/libraries/Microsoft.Win32.Registry/src/Microsoft.Win32.Registry.csproj +++ b/src/libraries/Microsoft.Win32.Registry/src/Microsoft.Win32.Registry.csproj @@ -27,6 +27,8 @@ Link="Common\Interop\Windows\Interop.RegCreateKeyEx.cs" /> + 0) - { - string[] keys = key.GetSubKeyNames(); - - for (int i = 0; i < keys.Length; i++) - { - key.DeleteSubKeyTreeInternal(keys[i]); - } - } - } + int ret = Interop.Advapi32.RegDeleteTree(_hkey, subkey); - DeleteSubKeyTreeCore(subkey); - } - else if (throwOnMissingSubKey) - { - throw new ArgumentException(SR.Arg_RegSubKeyAbsent); - } - } - - /// - /// 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. - /// - private void DeleteSubKeyTreeInternal(string subkey) - { - RegistryKey? key = InternalOpenSubKeyWithoutSecurityChecks(subkey, true); - if (key != null) + if (ret == Interop.Errors.ERROR_FILE_NOT_FOUND) { - using (key) + if (throwOnMissingSubKey) { - if (key.SubKeyCount > 0) - { - string[] keys = key.GetSubKeyNames(); - for (int i = 0; i < keys.Length; i++) - { - key.DeleteSubKeyTreeInternal(keys[i]); - } - } + throw new ArgumentException(SR.Arg_RegSubKeyAbsent); } - - 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) + else if (ret != 0) { Win32Error(ret, null); } From da3c445bb6b55a37e92656736a666f920c32d08d Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 2 Mar 2023 21:56:20 +0800 Subject: [PATCH 2/4] Restore self delete behavior --- .../src/Microsoft/Win32/RegistryKey.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs b/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs index 70d2788a56695d..ee05308eb6b18c 100644 --- a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs +++ b/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs @@ -347,6 +347,20 @@ public void DeleteSubKeyTree(string subkey, bool throwOnMissingSubKey) { Win32Error(ret, null); } + + if (string.IsNullOrEmpty(subkey)) + { + // If subkey is empty, the old implementation opens a new handle to current key + // and deletes current key. + // However, RegDeleteTree only delete subkeys and values of current key + // if subkey is null. + // Also delete current key to restore old behavior. + ret = Interop.Advapi32.RegDeleteKeyEx(_hkey, subkey, (int)_regView, 0); + if (ret != 0) + { + Win32Error(ret, null); + } + } } /// Deletes the specified value from this key. From a9e72c4e5a10f7a74f100abf34baf24e4d3429b9 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 2 May 2023 17:36:44 +0800 Subject: [PATCH 3/4] Call RegDeleteTree on subkey to simulate permission behavior. --- .../src/Microsoft/Win32/RegistryKey.cs | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs b/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs index ee05308eb6b18c..b11a2c99af371d 100644 --- a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs +++ b/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs @@ -334,31 +334,34 @@ public void DeleteSubKeyTree(string subkey, bool throwOnMissingSubKey) subkey = FixupName(subkey); // Fixup multiple slashes to a single slash - int ret = Interop.Advapi32.RegDeleteTree(_hkey, subkey); - - if (ret == Interop.Errors.ERROR_FILE_NOT_FOUND) + RegistryKey? key = InternalOpenSubKeyWithoutSecurityChecks(subkey, false); + if (key != null) { - if (throwOnMissingSubKey) + using (key) { - throw new ArgumentException(SR.Arg_RegSubKeyAbsent); + // The access requirement of RegDeleteTree is different with old implementation. + // Open a new handle to the subkey to restore old behavior. + int ret = Interop.Advapi32.RegDeleteTree(key._hkey, string.Empty); + if (ret != 0) + { + Win32Error(ret, null); + } + + // RegDeleteTree only delete subkeys and values of the key + // if subkey is null. + // Also delete the key. + ret = Interop.Advapi32.RegDeleteKeyEx(key._hkey, string.Empty, (int)_regView, 0); + if (ret != 0) + { + Win32Error(ret, null); + } } } - else if (ret != 0) - { - Win32Error(ret, null); - } - - if (string.IsNullOrEmpty(subkey)) + else { - // If subkey is empty, the old implementation opens a new handle to current key - // and deletes current key. - // However, RegDeleteTree only delete subkeys and values of current key - // if subkey is null. - // Also delete current key to restore old behavior. - ret = Interop.Advapi32.RegDeleteKeyEx(_hkey, subkey, (int)_regView, 0); - if (ret != 0) + if (throwOnMissingSubKey) { - Win32Error(ret, null); + throw new ArgumentException(SR.Arg_RegSubKeyAbsent); } } } From ec456762f6f7a48a28bb1e5dd14a9bf50656bbc2 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 12 Jul 2023 23:14:08 +0800 Subject: [PATCH 4/4] Adjust comment and add tests. --- .../src/Microsoft/Win32/RegistryKey.cs | 13 ++-- .../RegistryKey_DeleteSubKeyTree_str.cs | 64 ++++++++++++++++++- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs b/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs index b11a2c99af371d..834793e8504dec 100644 --- a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs +++ b/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs @@ -334,22 +334,23 @@ public void DeleteSubKeyTree(string subkey, bool throwOnMissingSubKey) subkey = FixupName(subkey); // Fixup multiple slashes to a single slash - RegistryKey? key = InternalOpenSubKeyWithoutSecurityChecks(subkey, false); + // 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) { - // The access requirement of RegDeleteTree is different with old implementation. - // Open a new handle to the subkey to restore old behavior. int ret = Interop.Advapi32.RegDeleteTree(key._hkey, string.Empty); if (ret != 0) { Win32Error(ret, null); } - // RegDeleteTree only delete subkeys and values of the key - // if subkey is null. - // Also delete the key. + // RegDeleteTree doesn't self-delete when lpSubKey is empty. + // Manually delete the key to restore old behavior. + ret = Interop.Advapi32.RegDeleteKeyEx(key._hkey, string.Empty, (int)_regView, 0); if (ret != 0) { diff --git a/src/libraries/Microsoft.Win32.Registry/tests/RegistryKey/RegistryKey_DeleteSubKeyTree_str.cs b/src/libraries/Microsoft.Win32.Registry/tests/RegistryKey/RegistryKey_DeleteSubKeyTree_str.cs index c8606e1a2de022..727087d466ccfc 100644 --- a/src/libraries/Microsoft.Win32.Registry/tests/RegistryKey/RegistryKey_DeleteSubKeyTree_str.cs +++ b/src/libraries/Microsoft.Win32.Registry/tests/RegistryKey/RegistryKey_DeleteSubKeyTree_str.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using System.Reflection; using Xunit; namespace Microsoft.Win32.RegistryTests @@ -49,6 +48,38 @@ public void SelfDeleteTest() 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 @@ public void DeleteSubKeyTreeTest2() 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))]