{"id":110704,"date":"2025-01-03T07:00:00","date_gmt":"2025-01-03T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=110704"},"modified":"2024-12-31T21:16:25","modified_gmt":"2025-01-01T05:16:25","slug":"20250103-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250103-00\/?p=110704","title":{"rendered":"Forcing an <CODE>ERROR_<WBR>KEY_<WBR>DELETED<\/CODE> error when trying to open <CODE>HKEY_<WBR>CURRENT_<WBR>USER\\<WBR>Software<\/CODE>"},"content":{"rendered":"<p>Last time, we wondered whether <a title=\"Could I be getting ERROR_KEY_DELETED for HKEY_CURRENT_USER\\Software when the user logs off?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250102-00\/?p=110702\"> the <code>ERROR_<wbr \/>KEY_<wbr \/>DELETED<\/code> error code could be the result of the user logging off while a program was running<\/a>. I noted that this is primarily an issue for services, though you can force it to happen for regular user-session applications.<\/p>\n<p>It involves taking advantage of the <code>Reg\u00adOverride\u00adPredef\u00adKey<\/code> which lets you redirect the registry operations performed by the current program to another registry key. The intention of this function was to allow you to override a registry root key like <code>HKEY_<wbr \/>CLASSES_<wbr \/>ROOT<\/code> <a title=\"On the failed unrealized promise of RegOverridePredefKey\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20231020-00\/?p=108911\"> so you can capture the registry keys written by an installer<\/a>. But we&#8217;re going to use it to force an <code>ERROR_<wbr \/>KEY_<wbr \/>DELETED<\/code> error from code that tries to open <code>HKEY_<wbr \/>CURRENT_<wbr \/>USER\\<wbr \/>Software<\/code>.<\/p>\n<p>Note that this is just a parlor trick! There is no practical use for this demonstration.<\/p>\n<pre>#include &lt;windows.h&gt;\r\n#include &lt;wil\/resource.h&gt;\r\n#include &lt;wil\/result_macros.h&gt;\r\n\r\nint main(int, char**)\r\n{\r\n    wil::unique_hkey originalSoftware;\r\n    FAIL_FAST_IF_WIN32_ERROR(RegOpenKeyExW(HKEY_CURRENT_USER,\r\n        L\"Software\", 0, KEY_READ, &amp;originalSoftware));\r\n\r\n    wil::unique_hkey originalContoso;\r\n    DWORD disposition;\r\n    FAIL_FAST_IF_WIN32_ERROR(RegCreateKeyExW(originalSoftware.get(),\r\n        L\"Contoso\", 0, nullptr, REG_OPTION_VOLATILE,\r\n        KEY_READ | KEY_WRITE, nullptr,\r\n        &amp;originalContoso, &amp;disposition));\r\n    if (disposition != REG_CREATED_NEW_KEY) {\r\n        printf(\"Unexpected HKCU\\\\Software\\\\Contoso key\\n\");\r\n        return 0;\r\n    }\r\n\r\n    FAIL_FAST_IF_WIN32_ERROR(RegOverridePredefKey(\r\n        HKEY_CURRENT_USER, originalContoso.get()));\r\n\r\n    FAIL_FAST_IF_WIN32_ERROR(\r\n        RegDeleteKeyW(originalSoftware.get(), L\"Contoso\"));\r\n\r\n    wil::unique_hkey key;\r\n    LSTATUS error = RegOpenKeyExW(HKEY_CURRENT_USER, L\"Software\", 0, KEY_READ, &amp;key);\r\n    if (error == ERROR_KEY_DELETED)\r\n    {\r\n        printf(\"It happened!\\n\");\r\n    } else {\r\n        printf(\"It didn't happen.\\n\");\r\n    }\r\n\r\n    return 0;\r\n}\r\n<\/pre>\n<p>First, we create a key to the original <code>HKEY_<wbr \/>CURRENT_<wbr \/>USER\\<wbr \/>Software<\/code>, and then create a <code>Contoso<\/code> subkey under it. If there was already a <code>Contoso<\/code> subkey, then we bail out because our trick won&#8217;t work. (We&#8217;ll have to pick another key to use as our sacrificial lamb.)<\/p>\n<p>After creating the <code>Contoso<\/code> subkey, we pass it to <code>Reg\u00adOverride\u00adPredef\u00adKey<\/code> to make it the new <code>HKEY_<wbr \/>CURRENT_<wbr \/>USER<\/code>. Any future references to <code>HKEY_<wbr \/>CURRENT_<wbr \/>USER<\/code> will use our <code>originalContoso<\/code> key instead of the original <code>HKEY_<wbr \/>CURRENT_<wbr \/>USER<\/code>. (Note that our original keys still refer to their original unredirected versions.)<\/p>\n<p>Our final step for preparing the trick is deleting the <code>Contoso<\/code> key from the original <code>HKEY_<wbr \/>CURRENT_<wbr \/>USER\\<wbr \/>Software<\/code>. Since we had redirected <code>HKEY_<wbr \/>CURRENT_<wbr \/>USER<\/code> to refer to the original <code>HKEY_<wbr \/>CURRENT_<wbr \/>USER\\<wbr \/>Software\\<wbr \/>Contoso<\/code> key, deleting that key means that <code>HKEY_<wbr \/>CURRENT_<wbr \/>USER<\/code> is now a reference to a deleted key.<\/p>\n<p>The next line of code is our victim. It tries to open <code>HKEY_<wbr \/>CURRENT_<wbr \/>USER\\<wbr \/>Software<\/code>, which is an attempt to reference a <code>Software<\/code> subkey under the original now-deleted <code>HKEY_<wbr \/>CURRENT_<wbr \/>USER\\<wbr \/>Software\\<wbr \/>Contoso<\/code> key. Since the key has been deleted, the error is <code>ERROR_<wbr \/>KEY_<wbr \/>DELETED<\/code>.<\/p>\n<p>I&#8217;m not saying this is what was causing the original code to get an <code>ERROR_<wbr \/>KEY_<wbr \/>DELETED<\/code> error when trying to open <code>HKEY_<wbr \/>CURRENT_<wbr \/>USER\\<wbr \/>Software<\/code>. I&#8217;m just saying that this is one way it can happen, though it is extremely contrived.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Just an exercise to show that it can be done, even though it&#8217;s highly unlikely to occur in practice.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-110704","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Just an exercise to show that it can be done, even though it&#8217;s highly unlikely to occur in practice.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110704","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=110704"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110704\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=110704"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=110704"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=110704"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}