Last article, we saw that Handles could be blocked from CloseHandle on a per-type basis. However, there are situations when you only want some Handles of a type to be blocked. This is addressed in today’s newest addition to the Handle System API article.
You may note that the article has been altered; there are now per-Handle permissions. Originally, there was no way to do this. In fact, this feature was added only a day ago. However, there are pieces of SourceMod already implemented which required the feature, so how was it done in the past?
We used the fact that child types do not inherit permissions from their parent. Imagine two types using BitBuffers as an example: a “read only BitBuffer” and a “writable BitBuffer.” First, you create a “writable BitBuffer” type. Then, you create a second, read-only type with the writable type as its parent. Because of the parent/child relationship, the same set of functions would work on both types; but CloseHandle() would only work on the writable version. This works because you can specify new security rules for child types.
Why did we opt for such a complex solution at first, rather than just implementing per-Handle permissions from the start? There’s no real excuse, but this was the mindset:
- We did not actually foresee this being useful enough at the time. Each Handle uses about 40 bytes in memory, and the block of pre-allocated Handles is 16K entries long. That’s roughly a 660KB block of consecutive memory, and per-Handle permissions would increase that to 60 bytes per Handle (~990KB), so we opted to not do it.
- The permissions system was already completed and in use by the time we realized the design flaw, and thus new functions would have to be added for backwards compatibility.
One thing to note about the prior workaround is that the order of inheritance does not matter. The parent OR the child type can be the locked type. The Handle System doesn’t care, as the types internally do not actually share anything. However, you must remember to use the parent type for generic accesses. If you use the child type, and a Handle of the parent type is passed in, the type check routine will fail. This is the same as in C++: You can’t necessarily cast a base type to a derived type safely.
Internally, the implementation of the new per-Handle security is optimized for backwards compatibility. The “quick” way to implement this, in pseudo code, would have been:
handle->access = access ? access : handle->type->access
This would make the Handle permissions equal to either user-supplied, or the type’s default, access rules. But the implementation is slightly different. CreateHandle() is now a wrapper around CreateHandleEx() (which itself wraps an internal function called CreateHandleInt(), which wraps MakePrimHandle!), and it passes NULL as the access structure — meaning that CreateHandle is general usage and will be called more often. So to prevent copying the entire permissions struct on every “normal” Handle create, it looks a bit different.
IF access
handle->special_rules = true
handle->access = access
ELSE
handle->special_rules = false
Internally, there is a simple CheckAccess() function which can decode this:
CheckAccess(handle, right, identity)
{
access = handle->special_rules ? handle->access : handle->type->access
if (access[right] & HANDLE_RESTRICT_IDENTITY
&& handle->identity != identity)
{
return false
}
return true
}
(Of course, this isn’t really an “optimization,” but it’s in case the HandleAccess struct grows more complex over time.)
That about sums it up for Handle Security. You, as the rest of the SourceMod developers are, should be thoroughly sick of it by now.