Reactos

[NTOS:CM] Lock the entire registry down when we unload a hive

The PR #6649 which fixed an issue with orphaned KCBs leaking in memory which also pointed to unloaded registry hives, it also brought a problem.
In CmpEnumerateOpenSubKeys there is a risk of getting hit by a deadlock as we enumerate the cache table to remove empty cache entries.

Fundamentally CmpEnumerateOpenSubKeys locks down a KCB from cache for exclusive use in order to tear down its contents from memory but it doesn't address the fact a KCB might have already been locked in the same calling thread, leading to a recursion.
This leads to random hangs when unloading a hive during system startup (tipically on a clean install).

The solution here is to simply lock the whole registry when we unload a hive so that we don't have to worry the KCBs are getting tampered by anybody else. This also simplifies the code.
Although locking the entire registry while other apps are doing registry related operations to other hives can cause overhead. If this turns out to be bad then we have to rethink the locking mechanism here.

CORE-19539

+25 -75
+11 -24
ntoskrnl/config/cmapi.c
··· 2216 2216 2217 2217 DPRINT("CmUnloadKey(%p, %lx)\n", Kcb, Flags); 2218 2218 2219 + /* Ensure the registry is locked exclusively for the calling thread */ 2220 + CMP_ASSERT_EXCLUSIVE_REGISTRY_LOCK(); 2221 + 2219 2222 /* Get the hive */ 2220 2223 Hive = Kcb->KeyHive; 2221 2224 Cell = Kcb->KeyCell; ··· 2243 2246 { 2244 2247 if (Flags != REG_FORCE_UNLOAD) 2245 2248 { 2246 - if (CmpEnumerateOpenSubKeys(Kcb, FALSE, TRUE, FALSE) != 0) 2249 + if (CmpEnumerateOpenSubKeys(Kcb, TRUE, FALSE) != 0) 2247 2250 { 2248 2251 /* There are open subkeys but we don't force hive unloading, fail */ 2249 2252 Hive->HiveFlags &= ~HIVE_IS_UNLOADING; ··· 2252 2255 } 2253 2256 else 2254 2257 { 2255 - if (CmpEnumerateOpenSubKeys(Kcb, TRUE, TRUE, TRUE) != 0) 2258 + if (CmpEnumerateOpenSubKeys(Kcb, TRUE, TRUE) != 0) 2256 2259 { 2257 2260 /* There are open subkeys that we cannot force to unload, fail */ 2258 2261 Hive->HiveFlags &= ~HIVE_IS_UNLOADING; ··· 2293 2296 Kcb->Delete = TRUE; 2294 2297 CmpRemoveKeyControlBlock(Kcb); 2295 2298 2296 - if (Flags != REG_FORCE_UNLOAD) 2297 - { 2298 - /* Release the KCB locks */ 2299 - CmpReleaseTwoKcbLockByKey(Kcb->ConvKey, Kcb->ParentKcb->ConvKey); 2300 - 2301 - /* Release the hive loading lock */ 2302 - ExReleasePushLockExclusive(&CmpLoadHiveLock); 2303 - } 2299 + /* Release the hive loading lock */ 2300 + ExReleasePushLockExclusive(&CmpLoadHiveLock); 2304 2301 2305 2302 /* Release hive lock */ 2306 2303 CmpUnlockRegistry(); ··· 2341 2338 NTAPI 2342 2339 CmpEnumerateOpenSubKeys( 2343 2340 _In_ PCM_KEY_CONTROL_BLOCK RootKcb, 2344 - _In_ BOOLEAN LockHeldExclusively, 2345 2341 _In_ BOOLEAN RemoveEmptyCacheEntries, 2346 2342 _In_ BOOLEAN DereferenceOpenedEntries) 2347 2343 { ··· 2354 2350 2355 2351 DPRINT("CmpEnumerateOpenSubKeys() called\n"); 2356 2352 2353 + /* Ensure the registry is locked exclusively for the calling thread */ 2354 + CMP_ASSERT_EXCLUSIVE_REGISTRY_LOCK(); 2355 + 2357 2356 /* The root key is the only referenced key. There are no referenced sub keys. */ 2358 2357 if (RootKcb->RefCount == 1) 2359 2358 { ··· 2401 2400 if (DereferenceOpenedEntries && 2402 2401 !(CachedKcb->ExtFlags & CM_KCB_READ_ONLY_KEY)) 2403 2402 { 2404 - /* Registry needs to be locked down */ 2405 - CMP_ASSERT_EXCLUSIVE_REGISTRY_LOCK(); 2406 - 2407 2403 /* Flush any notifications */ 2408 2404 CmpFlushNotifiesOnKeyBodyList(CachedKcb, TRUE); // Lock is already held 2409 2405 ··· 2431 2427 } 2432 2428 else if ((CachedKcb->RefCount == 0) && RemoveEmptyCacheEntries) 2433 2429 { 2434 - /* Lock the cached KCB of subkey before removing it from cache entries */ 2435 - if (!LockHeldExclusively) 2436 - CmpAcquireKcbLockExclusive(CachedKcb); 2437 - 2438 2430 /* Remove the current key from the delayed close list */ 2439 2431 CmpRemoveFromDelayedClose(CachedKcb); 2440 2432 2441 2433 /* Remove the current cache entry */ 2442 - // Lock is either held by ourselves or registry is locked exclusively 2443 - CmpCleanUpKcbCacheWithLock(CachedKcb, LockHeldExclusively); 2444 - 2445 - /* Unlock the cached KCB if it was done by ourselves */ 2446 - if (!LockHeldExclusively) 2447 - CmpReleaseKcbLock(CachedKcb); 2434 + CmpCleanUpKcbCacheWithLock(CachedKcb, TRUE); 2448 2435 2449 2436 /* Restart, because the hash list has changed */ 2450 2437 Entry = CmpCacheTable[i].Entry;
+14 -50
ntoskrnl/config/ntapi.c
··· 1565 1565 1566 1566 /* Call the internal API */ 1567 1567 SubKeys = CmpEnumerateOpenSubKeys(KeyBody->KeyControlBlock, 1568 - TRUE, FALSE, FALSE); 1568 + FALSE, FALSE); 1569 1569 1570 1570 /* Unlock the registry */ 1571 1571 CmpUnlockRegistry(); ··· 1803 1803 CM_PARSE_CONTEXT ParseContext = {0}; 1804 1804 KPROCESSOR_MODE PreviousMode = ExGetPreviousMode(); 1805 1805 PCM_KEY_BODY KeyBody = NULL; 1806 - ULONG ParentConv = 0, ChildConv = 0; 1807 1806 HANDLE Handle; 1808 1807 1809 1808 PAGED_CODE(); ··· 1895 1894 if (!NT_SUCCESS(Status)) 1896 1895 return Status; 1897 1896 1898 - /* Acquire the lock depending on flags */ 1899 - if (Flags == REG_FORCE_UNLOAD) 1900 - { 1901 - /* Lock registry exclusively */ 1902 - CmpLockRegistryExclusive(); 1903 - } 1904 - else 1905 - { 1906 - /* Lock registry */ 1907 - CmpLockRegistry(); 1908 - 1909 - /* Acquire the hive loading lock */ 1910 - ExAcquirePushLockExclusive(&CmpLoadHiveLock); 1911 - 1912 - /* Lock parent and child */ 1913 - if (KeyBody->KeyControlBlock->ParentKcb) 1914 - ParentConv = KeyBody->KeyControlBlock->ParentKcb->ConvKey; 1915 - else 1916 - ParentConv = KeyBody->KeyControlBlock->ConvKey; 1917 - 1918 - ChildConv = KeyBody->KeyControlBlock->ConvKey; 1919 - 1920 - CmpAcquireTwoKcbLocksExclusiveByKey(ChildConv, ParentConv); 1921 - } 1897 + /* 1898 + * Lock down the entire registry when we unload a hive. 1899 + * 1900 + * NOTE: We might block other threads of other processes that do 1901 + * operations with unrelated keys of other hives when we lock 1902 + * the registry for exclusive use by the calling thread that does 1903 + * the unloading. If this turns out to cause a major overhead we 1904 + * have to rethink the locking mechanism here (prior commit - f1d2a44). 1905 + */ 1906 + CmpLockRegistryExclusive(); 1907 + ExAcquirePushLockExclusive(&CmpLoadHiveLock); 1922 1908 1923 1909 /* Check if it's being deleted already */ 1924 1910 if (KeyBody->KeyControlBlock->Delete) ··· 1939 1925 /* Call the internal API. Note that CmUnloadKey() unlocks the registry only on success. */ 1940 1926 Status = CmUnloadKey(KeyBody->KeyControlBlock, Flags); 1941 1927 1942 - /* Check if we failed, but really need to succeed */ 1943 - if ((Status == STATUS_CANNOT_DELETE) && (Flags == REG_FORCE_UNLOAD)) 1944 - { 1945 - /* TODO: We should perform another attempt here */ 1946 - _SEH2_TRY 1947 - { 1948 - DPRINT1("NtUnloadKey2(%wZ): We want to force-unload the hive but couldn't unload it: Retrying is UNIMPLEMENTED!\n", TargetKey->ObjectName); 1949 - } 1950 - _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 1951 - { 1952 - } 1953 - _SEH2_END; 1954 - } 1955 - 1956 1928 Quit: 1957 1929 /* If CmUnloadKey() failed we need to unlock registry ourselves */ 1958 1930 if (!NT_SUCCESS(Status)) 1959 1931 { 1960 - if (Flags != REG_FORCE_UNLOAD) 1961 - { 1962 - /* Release the KCB locks */ 1963 - CmpReleaseTwoKcbLockByKey(ChildConv, ParentConv); 1964 - 1965 - /* Release the hive loading lock */ 1966 - ExReleasePushLockExclusive(&CmpLoadHiveLock); 1967 - } 1968 - 1969 - /* Unlock the registry */ 1932 + /* Unlock the hive loading and registry locks */ 1933 + ExReleasePushLockExclusive(&CmpLoadHiveLock); 1970 1934 CmpUnlockRegistry(); 1971 1935 } 1972 1936
-1
ntoskrnl/include/internal/cm.h
··· 1337 1337 NTAPI 1338 1338 CmpEnumerateOpenSubKeys( 1339 1339 _In_ PCM_KEY_CONTROL_BLOCK RootKcb, 1340 - _In_ BOOLEAN LockHeldExclusively, 1341 1340 _In_ BOOLEAN RemoveEmptyCacheEntries, 1342 1341 _In_ BOOLEAN DereferenceOpenedEntries 1343 1342 );