Repository: microsoft/wil (C++)

Issue:
`get_token_information` incorrectly handles memory allocation/deallocation
https://github.com/microsoft/wil/blob/7faa90d7185c8617f73694c5382b21cf4ed05825/include/wil/token_helpers.h#L163-L179

Essentially, the function directly calls `operator new` to allocate a buffer of memory:

https://github.com/microsoft/wil/blob/7faa90d7185c8617f73694c5382b21cf4ed05825/include/wil/token_helpers.h#L172-L173

and later casts the returned pointer to some structure type `T` (like `TOKEN_USER`):

https://github.com/microsoft/wil/blob/7faa90d7185c8617f73694c5382b21cf4ed05825/include/wil/token_helpers.h#L176

and finally calls `delete ptr`, where `ptr` has type `T *` (like `TOKEN_USER *`).

https://github.com/microsoft/wil/blob/7faa90d7185c8617f73694c5382b21cf4ed05825/include/wil/wistd_memory.h#L281

This does not seem to be legitimate (however "seems to work").

This issue is also reported by ASAN in Visual Studio 2022 17.4.0.



Discussion: Agreed. It "works" in that everything boils down to heapalloc/heapfree.  Somebody with a custom allocator or a tracking allocator would Sadly this is going to be a hard change to land. I'll need to change the return/output types to be a `wil::unique_token_info<T>` that is-a `wistd::unique_ptr<T, TokenInfoDeleter>` instead. Probably should have been that way from the get-go. @dunhor this is likely going to be a large impact to ingestion into the OS repo.
Agree on the solution. The tricky ones will be `get_token_information_nothrow` and `get_token_information_failfast` as the caller needs to explicitly declare the result's type as it is an output parameter. I.e. uses of these _will_ need source changes. Searching for these two yields 76 files - let's call it 70 when we account for WIL itself and its tests. That's a lot, but not the worst we've ever had to go in and change. There's also hits in 80 files for `get_token_information` and I'm unsure how many of them don't use `auto`, or store it as a member, or pass it as input to another function, or need to forward declare outside of lock scope, etc.

It's typically better and more preferrable if these types of changes are made directly in the OS source code.
Now I'm curious if I misunderstood the warning/error that led to me adding [this](https://github.com/microsoft/wil/blob/3e2ee372dcbbe77977934fbe0c58ff8ceb0236ee/tests/main.cpp#L17) when enabling the ASan/UBSan tests...
Agree - "overallocate" is not the same as "mismatched allocator/freer" ... for the token case, it's reasonable to overallocate a chunk of memory so that a `struct SHOUTY_TYPE { DWORD x; ULONG_PTR yvals[1]; };` has enough space to store "x" yvals. But if that allocator called `HeapAlloc` but passed it to a unique_ptr that calls delete, that's the problem we want to avoid.
The error message is _slightly_ confusing, but I definitely did mis-read it:
```
==32156==ERROR: AddressSanitizer: new-delete-type-mismatch on 0x12392a922290 in thread T0:
  object passed to delete has wrong type:
  size of the allocated type:   44 bytes;
  size of the deallocated type: 16 bytes.
    #0 0x7ff681a3f13d in operator delete(void *, unsigned __int64) D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_win_delete_scalar_size_thunk.cpp:42
    #1 0x7ff6813efd26 in C_A_T_C_H_T_E_S_T_4 G:\wil\tests\TokenHelpersTests.cpp:49
    #2 0x7ff680735710 in Catch::RunContext::invokeActiveTestCase(void) G:\wil\tests\catch.hpp:13025
    #3 0x7ff680753056 in Catch::RunContext::runCurrentTest(class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char>> &, class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char>> &) G:\wil\tests\catch.hpp:12998
    #4 0x7ff68075397d in Catch::RunContext::runTest(class Catch::TestCase const &) G:\wil\tests\catch.hpp:12759
    #5 0x7ff68072d74d in Catch::`anonymous namespace'::TestGroup::execute G:\wil\tests\catch.hpp:13352
    #6 0x7ff68075343b in Catch::Session::runInternal(void) G:\wil\tests\catch.hpp:13562
    #7 0x7ff680752b88 in Catch::Session::run(void) G:\wil\tests\catch.hpp:13518
    #8 0x7ff680769200 in main G:\wil\tests\catch.hpp:17543
    #9 0x7ff681a460a3 in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
    #10 0x7ff928ea292c  (C:\WINDOWS\System32\KERNEL32.DLL+0x18001292c)
    #11 0x7ff929f7f137  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18004f137)

0x12392a922290 is located 0 bytes inside of 44-byte region [0x12392a922290,0x12392a9222bc)
allocated by thread T0 here:
    #0 0x7ff681a3f1a8 in operator new(unsigned __int64, struct std::nothrow_t const &) D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_win_new_scalar_nothrow_thunk.cpp:40
    #1 0x7ff6813ef6dd in C_A_T_C_H_T_E_S_T_4 G:\wil\tests\TokenHelpersTests.cpp:47
    #2 0x7ff680735710 in Catch::RunContext::invokeActiveTestCase(void) G:\wil\tests\catch.hpp:13025
    #3 0x7ff680753056 in Catch::RunContext::runCurrentTest(class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char>> &, class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char>> &) G:\wil\tests\catch.hpp:12998
    #4 0x7ff68075397d in Catch::RunContext::runTest(class Catch::TestCase const &) G:\wil\tests\catch.hpp:12759
    #5 0x7ff68072d74d in Catch::`anonymous namespace'::TestGroup::execute G:\wil\tests\catch.hpp:13352
    #6 0x7ff68075343b in Catch::Session::runInternal(void) G:\wil\tests\catch.hpp:13562
    #7 0x7ff680752b88 in Catch::Session::run(void) G:\wil\tests\catch.hpp:13518
    #8 0x7ff680769200 in main G:\wil\tests\catch.hpp:17543
    #9 0x7ff681a460a3 in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
    #10 0x7ff928ea292c  (C:\WINDOWS\System32\KERNEL32.DLL+0x18001292c)
    #11 0x7ff929f7f137  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18004f137)

SUMMARY: AddressSanitizer: new-delete-type-mismatch D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_win_delete_scalar_size_thunk.cpp:42 in operator delete(void *, unsigned __int64)
==32156==HINT: if you don't care about these errors you may set ASAN_OPTIONS=new_delete_type_mismatch=0
```
The output explicitly does say `object passed to delete has wrong type`, but then the following line has object byte size information. I misinterpreted that to mean that ASan was unhappy with the allocation size. This was further compacted by the fact that Clang doesn't seem to give this warning.
For folks playing along from home, http://task.ms/43619989 is the OS work item.



Fix (patch):
diff --git a/include/wil/token_helpers.h b/include/wil/token_helpers.h
index aec3952b..1b4e927e 100644
--- a/include/wil/token_helpers.h
+++ b/include/wil/token_helpers.h
@@ -55,6 +55,15 @@ namespace wil
         template<> struct MapTokenStructToInfoClass<TOKEN_TYPE> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenType; static constexpr bool FixedSize = true; };
         template<> struct MapTokenStructToInfoClass<SECURITY_IMPERSONATION_LEVEL> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenImpersonationLevel;  static constexpr bool FixedSize = true; };
         template<> struct MapTokenStructToInfoClass<TOKEN_ELEVATION> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenElevation; static constexpr bool FixedSize = true; };
+    
+        struct token_info_deleter
+        {
+            template<typename T> void operator()(T* p) const
+            {
+                static_assert(wistd::is_trivially_destructible_v<T>, "do not use with nontrivial types");
+                ::operator delete(p);
+            }
+        };
     }
     /// @endcond
 
@@ -134,19 +143,19 @@ namespace wil
 
     /** Fetches information about a token.
     See GetTokenInformation on MSDN for what this method can return. For variable sized structs the information
-    is returned to the caller as a wistd::unique_ptr<T> (like TOKEN_ORIGIN, TOKEN_USER, TOKEN_ELEVATION, etc.). For
+    is returned to the caller as a wil::unique_tokeninfo_ptr<T> (like TOKEN_ORIGIN, TOKEN_USER, TOKEN_ELEVATION, etc.). For
     fixed sized, the struct is returned directly.
     The caller must have access to read the information from the provided token. This method works with both real
     (e.g. OpenCurrentAccessToken) and pseudo (e.g. GetCurrentThreadToken) token handles.
     ~~~~
     // Retrieve the TOKEN_USER structure for the current process
-    wistd::unique_ptr<TOKEN_USER> user;
+    wil::unique_tokeninfo_ptr<TOKEN_USER> user;
     RETURN_IF_FAILED(wil::get_token_information_nothrow(user, GetCurrentProcessToken()));
     RETURN_IF_FAILED(ConsumeSid(user->User.Sid));
     ~~~~
     Not specifying the token handle is the same as specifying 'nullptr' and retrieves information about the effective token.
     ~~~~
-    wistd::unique_ptr<TOKEN_PRIVILEGES> privileges;
+    wil::unique_tokeninfo_ptr<TOKEN_PRIVILEGES> privileges;
     RETURN_IF_FAILED(wil::get_token_information_nothrow(privileges));
     for (auto const& privilege : wil::GetRange(privileges->Privileges, privileges->PrivilegeCount))
     {
@@ -159,8 +168,10 @@ namespace wil
     @return S_OK on success, a FAILED hresult containing the win32 error from querying the token otherwise.
     */
 
+    template <typename Q> using unique_tokeninfo_ptr = wistd::unique_ptr<Q, details::token_info_deleter>;
+
     template <typename T, wistd::enable_if_t<!details::MapTokenStructToInfoClass<T>::FixedSize>* = nullptr>
-    inline HRESULT get_token_information_nothrow(wistd::unique_ptr<T>& tokenInfo, HANDLE tokenHandle = nullptr)
+    inline HRESULT get_token_information_nothrow(unique_tokeninfo_ptr<T>& tokenInfo, HANDLE tokenHandle = nullptr)
     {
         tokenInfo.reset();
         tokenHandle = GetCurrentThreadEffectiveTokenWithOverride(tokenHandle);
@@ -169,11 +180,10 @@ namespace wil
         const auto infoClass = details::MapTokenStructToInfoClass<T>::infoClass;
         RETURN_LAST_ERROR_IF(!((!GetTokenInformation(tokenHandle, infoClass, nullptr, 0, &tokenInfoSize)) &&
             (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)));
-        wistd::unique_ptr<char> tokenInfoClose(
-            static_cast<char*>(operator new(tokenInfoSize, std::nothrow)));
-        RETURN_IF_NULL_ALLOC(tokenInfoClose.get());
+        unique_tokeninfo_ptr<T> tokenInfoClose{ static_cast<T*>(::operator new(tokenInfoSize, std::nothrow)) };
+        RETURN_IF_NULL_ALLOC(tokenInfoClose);
         RETURN_IF_WIN32_BOOL_FALSE(GetTokenInformation(tokenHandle, infoClass, tokenInfoClose.get(), tokenInfoSize, &tokenInfoSize));
-        tokenInfo.reset(reinterpret_cast<T *>(tokenInfoClose.release()));
+        tokenInfo = wistd::move(tokenInfoClose);
 
         return S_OK;
     }
@@ -194,9 +204,9 @@ namespace wil
     namespace details
     {
         template<typename T, typename policy, wistd::enable_if_t<!details::MapTokenStructToInfoClass<T>::FixedSize>* = nullptr>
-        wistd::unique_ptr<T> GetTokenInfoWrap(HANDLE token = nullptr)
+        unique_tokeninfo_ptr<T> GetTokenInfoWrap(HANDLE token = nullptr)
         {
-            wistd::unique_ptr<T> temp;
+            unique_tokeninfo_ptr<T> temp;
             policy::HResult(get_token_information_nothrow(temp, token));
             return temp;
         }