All of the handles a process can own – which are a whopping 16 million, starting with Windows 2000 – are basically only indexes into a so-called
handle-table. This
handle table exists on a per-process basis and is maintained by the object manager. The entry of a
handle-table contains the pointer to the object body and the
access mask which was used to obtain the
handle (e.g. GENERIC_READ, GENERIC_WRITE). This allows the object manager to check the security constraints for any
handle upon
access and consequently to allow or deny
access to the underlying object.
Why are handles needed if they are anyway only pointers to the real objects? Well, first of all any driver writer knows that user-mode code is evil code. That is why security restrictions only apply to user-mode code. Basically any kernel-mode component such as drivers can
access objects without such restrictions via their pointer. Taking this into account means that giving user-mode code direct unrestricted
access to an object’s pointer would be simply insane from the security perspective. Furthermore the user-mode code may put own restrictions on how it wants to
access the object. For example it could open a file in read-only mode. So, giving user-mode code unrestricted
access to an object is inappropriate again, because such restrictions apply to the
handle of an object but not to the pointer of the object - to which the
handle is an index.