The lowest two bits of a kernel handle are called "tag bits" and are available for application use. This has nothing to do with the size of an entry in the handle table.
The comment in ntdef.h
(in Include\10.0.x.x\shared
) says:
//
// Low order two bits of a handle are ignored by the system and available
// for use by application code as tag bits. The remaining bits are opaque
// and used to store a serial number and table index.
//
#define OBJ_HANDLE_TAGBITS 0x00000003L
My guess is that it's a similar misuse like using the most significant bit of 32 bit pointers as a boolean flag, which is why we have LAA (large address aware) and non-LAA applications.
You could (but should not) add 1, 2 or 3 to a HANDLE and it should not affect other Windows API methods. E.g. WaitForSingleObject()
:
#include <iostream>
#include <windows.h>
int main()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
auto created = CreateProcess(L"C:\\Windows\\System32\\cmd.exe",
nullptr, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi
);
if (created)
{
pi.hProcess = static_cast<byte*>(pi.hProcess) + 3;
const auto result = WaitForSingleObject(pi.hProcess, INFINITE);
if (result == 0)
std::cout << "Completed!\n";
else
std::cout << "Failed!\n" << result << "\n";
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
else
std::cout << "Not created";
}