{"id":105973,"date":"2021-11-26T07:00:00","date_gmt":"2021-11-26T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105973"},"modified":"2021-11-25T06:32:40","modified_gmt":"2021-11-25T14:32:40","slug":"20211126-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20211126-00\/?p=105973","title":{"rendered":"I called AdjustTokenPrivileges, but I was still told that a necessary privilege was not held, redux"},"content":{"rendered":"<p>A customer had a service and wanted to change some token information. The information that they wanted to change required <code>Se\u00adTcb\u00adPrivilege<\/code>, so they adjusted their token privileges to enable that privilege, but the call still failed with <code>ERROR_<\/code><code>PRIVILEGE_<\/code><code>NOT_<\/code><code>HELD<\/code>: &#8220;A required privilege is not held by the client.&#8221;<\/p>\n<p>They followed the cookbook from <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190531-00\/?p=102532\"> the last time we tried to address this problem<\/a>. All calls succeed except the last one.<\/p>\n<pre>ImpersonateSelf(SecurityImpersonation);\r\n\r\nHANDLE threadToken;\r\nOpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS,\r\n                TRUE, &amp;threadToken);\r\n\r\nTOKEN_PRIVILEGES privileges;\r\nprivileges.PrivilegeCount = 1;\r\nprivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\r\nLookupPrivilegeValue(nullptr, SE_TCB_NAME,\r\n                     &amp;privileges.Privileges[0].Luid);\r\n\r\nTOKEN_PRIVILEGES changedPrivileges;\r\nAdjustTokenPrivileges(threadToken, FALSE, &amp;privileges,\r\n                      sizeof(changedPrivileges),\r\n                      &amp;changedPrivileges,\r\n                      nullptr);\r\n\r\nif (changedPrivileges.PrivilegeCount == 0)\r\n{\r\n  \/\/ No net changes to privileges occurred,\r\n  \/\/ so we must have had TCB already.\r\n  Log(\"Already had TCB privilege\");\r\n}\r\n\r\n\/\/ Now do the thing that requires TCB privilege.\r\n\r\nHANDLE newToken;\r\nDuplicateTokenEx(threadToken, TOKEN_ALL_ACCESS, nullptr,\r\n                 SECURITY_MAX_IMPERSONATION_LEVEL,\r\n                 TokenPrimary, &amp;newToken);\r\n\r\nDWORD sessionId = ...;\r\nSetTokenInformation(newToken, TokenSessionId,\r\n                    &amp;sessionId, sizeof(sessionId));\r\n\/\/ ^^ this fails\r\n<\/pre>\n<p>What&#8217;s going on?<\/p>\n<p>There is a horrible non-obvious quirk of the <code>Adjust\u00adToken\u00adPrivileges<\/code> function that is tripping us up: The function returns success <i>even though it may have failed to do what you asked<\/i>.<\/p>\n<p>The function &#8220;succeeded&#8221; in the sense that it successfully attempted to adjust the privileges you requested, and it successfully reported the result of the adjustment attempt. But that doesn&#8217;t mean that the attempt actually accomplished what you asked it to do.<\/p>\n<blockquote class=\"q\">\n<p>If the function succeeds, the return value is nonzero. To determine whether the function adjusted all of the specified privileges, call <b>GetLastError<\/b>, which returns one of the following values when the function succeeds:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th style=\"border-bottom: solid 1px gray;\">Return code<\/th>\n<th style=\"border-bottom: solid 1px gray;\">Description<\/th>\n<\/tr>\n<tr>\n<td style=\"border-bottom: solid 1px gray;\"><b>ERROR_SUCCESS<\/b><\/td>\n<td style=\"border-bottom: solid 1px gray;\">The function adjusted all specified privileges.<\/td>\n<\/tr>\n<tr>\n<td style=\"border-bottom: solid 1px gray;\"><b>ERROR_NOT_ALL_ASSIGNED<\/b><\/td>\n<td style=\"border-bottom: solid 1px gray;\">The token does not have one or more of the privileges specified in the <i>NewState<\/i> parameter. The function may succeed with this error value <u>even if no privileges were adjusted<\/u>. The <i>PreviousState<\/i> parameter indicates the privileges that were adjusted.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/blockquote>\n<p>Emphasis mine.<\/p>\n<p>Therefore, we must also check whether all of the requested privileges were actually adjusted.<\/p>\n<pre>TOKEN_PRIVILEGES changedPrivileges;\r\nif (!AdjustTokenPrivileges(threadToken, FALSE, &amp;privileges,\r\n                           sizeof(changedPrivileges),\r\n                           &amp;changedPrivileges,\r\n                           nullptr)) {\r\n  clean up and fail;\r\n}\r\n\r\n<span style=\"color: blue;\">if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)\r\n{\r\n  clean up and fail;\r\n}<\/span>\r\n<\/pre>\n<p>This case is simple because we are adjusting only one privilege. If we were adjusting more than one privilege, then we would have to call <code>Adjust\u00adToken\u00adPrivileges<\/code> a second time with the <code>changed\u00adPrivileges<\/code> to restore the token&#8217;s privileges to their original state. If you forget to do this, you end up returning with a token that has extra privileges enabled, which could be a security issue.<\/p>\n<pre>if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)\r\n{\r\n  <span style=\"color: blue;\">\/\/ Need to clean up the partially-adjusted token.\r\n  AdjustTokenPrivileges(threadToken, FALSE, &amp;changedPrivileges,\r\n                        sizeof(changedPrivileges), nullptr, nullptr);<\/span>\r\n  clean up and fail;\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Did any privilege adjustment occur at all?<\/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-105973","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Did any privilege adjustment occur at all?<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105973","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=105973"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105973\/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=105973"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105973"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105973"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}