{"id":102532,"date":"2019-05-31T07:00:00","date_gmt":"2019-05-31T14:00:00","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/oldnewthing\/?p=102532"},"modified":"2019-05-30T17:20:32","modified_gmt":"2019-05-31T00:20:32","slug":"20190531-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190531-00\/?p=102532","title":{"rendered":"I called AdjustTokenPrivileges, but I was still told that a necessary privilege was not held"},"content":{"rendered":"<p>A customer had a service running as Local System 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>Here&#8217;s sketch of their code. All function calls succeed except the last one.<\/p>\n<pre>HANDLE processToken;\r\nOpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS,\r\n    &amp;processToken);\r\n\r\nHANDLE newToken;\r\nDuplicateTokenEx(processToken, TOKEN_ALL_ACCESS, nullptr,\r\n    SECURITY_MAX_IMPERSONATION_LEVEL, TokenPrimary, &amp;newToken);\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\nAdjustTokenPrivileges(newToken, FALSE, &amp;privileges, 0,\r\n    nullptr, nullptr);\r\n\r\nDWORD sessionId = ...;\r\nSetTokenInformation(newToken, TokenSessionId, &amp;sesionId,\r\n    sizeof(sessionId)); \/\/ FAILS!\r\n<\/pre>\n<p>This fails because we adjusted the privileges of the wrong token!<\/p>\n<p>The TCB privilege needs to be enabled on the token that is performing the operation, not the token that is the target of the operation. Because you need privileges to <i>do things<\/i>, not to <i>have things done to you<\/i>.<\/p>\n<p>The security folks explained that the correct order of operations is<\/p>\n<ol>\n<li><code>Impersonate\u00adSelf()<\/code>.<\/li>\n<li><code>Open\u00adThread\u00adToken()<\/code>.<\/li>\n<li><code>Adjust\u00adToken\u00adPrivileges(threadToken)<\/code>.<\/li>\n<li>Do the thing you wanna do. (In this case, duplicate the token and change the session ID.)<\/li>\n<li>Close the thread token.<\/li>\n<li><code>Revert\u00adTo\u00adSelf()<\/code>.<\/li>\n<\/ol>\n<p>The overall sequence therefore goes like this:<\/p>\n<pre>void DoSomethingAwesome()\r\n{\r\n if (ImpersonateSelf(SecurityImpersonation)) {\r\n  HANDLE threadToken;\r\n  if (OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS,\r\n                      TRUE, &amp;threadToken)) {\r\n   TOKEN_PRIVILEGES privileges;\r\n   privileges.PrivilegeCount = 1;\r\n   privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\r\n   if (LookupPrivilegeValue(nullptr, SE_TCB_NAME,\r\n                            &amp;privileges.Privileges[0].Luid)) {\r\n    if (AdjustTokenPrivileges(newToken, FALSE, &amp;privileges, 0,\r\n                              nullptr, nullptr)) {\r\n     \/\/ Now do the thing you wanna do.\r\n     HANDLE newToken;\r\n     if (DuplicateTokenEx(threadToken, TOKEN_ALL_ACCESS, nullptr,\r\n                          SECURITY_MAX_IMPERSONATION_LEVEL,\r\n                          TokenPrimary, &amp;newToken)) {\r\n      DWORD sessionId = ...;\r\n      if (SetTokenInformation(newToken, TokenSessionId,\r\n                              &amp;sesionId, sizeof(sessionId))) {\r\n       \/\/ Hooray\r\n      }\r\n      CloseHandle(newToken);\r\n     }\r\n    }\r\n   }\r\n   CloseHandle(threadToken);\r\n  }\r\n  RevertToSelf();\r\n }\r\n}\r\n<\/pre>\n<p>Of course, in real life, you probably would use RAII types to ensure that handles get closed and to remember to <code>Revert\u00adTo\u00adSelf()<\/code> after a successful <code>Impersonate\u00adSelf()<\/code>.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Which token did you adjust?<\/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-102532","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Which token did you adjust?<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102532","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=102532"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102532\/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=102532"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=102532"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=102532"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}