The security check happens at the acquisition of the handle

Raymond Chen

Raymond

When you call a function like Create­File or Open­Mutex, a security check is made to ensure that you have access to the underlying object before returning a handle.

But once you get the handle, it’s yours to keep.

Changes to the security of the underlying object affect the security checks for future attempts to obtain handles to the object, but existing handles continue to have the access they have.

This behavior is a feature, not a bug.

Impersonation relies on this feature: After you impersonate a user, you can open a file that the user has access to, but any handles you opened prior to impersonation can still be used. And then after you revert impersonation, you can continue to use that handle obtained while impersonating, even though you are not impersonating any longer.

Allowing already-obtained handles to continue to be used means that programs that do impersonation can operate on objects on “both sides of the fence”: It can simultaneously access resources that are accessible only to to the original user, as well as resources that are accessible only to the impersonated user.

This technique is also used by server processes. The client requests a resource, and the server uses its server powers to open a handle to that resource, then returns the handle to the client via duplication. Thus, the client obtains access to a resource that the client itself may not have had direct access to. Since a handle is returned, the client can operate directly with the resource via that handle, which is more efficient (and less work) than having to broker every operation on the resource through the server.

Performing the security check at handle creation allows expensive work to be front-loaded. Enumerating the ACEs, matching them up against the identities in the SID, generating any requested audit entries into the security event log: That can happen just once, at the handle creation. Subsequent actions need only check the granted access mask that is recorded in the handle.

However, this behavior also means that if you change the security on a resource, the change affects only future attempts to obtain a handle to that resource. Pre-existing handles continue to have whatever level of access they already had. This means that if you, say, make a file read-only, anybody who had already obtained a write handle to the file can still write to it via that handle. They won’t be able to make any new write handles, but they can use the one they already have.

If you really want to make sure nobody can write to the file any more, you need to make sure nobody got “grandfathered in”. If the file is on a server and you are concerned about a client with a lingering write handle, you can use the Computer Management snap-in, go to Shared Folders, Open Files, and then close any existing handles to the file you are worried about.¹ If you are worried about local access, you can restart the computer that has the file.

¹ Note that closing handles via the snap-in is not the same as forcing handles closed by injecting a Close­Handle into the client process. The snap-in invalidates the handle on the server, but the handle is still valid on the client. When the client tries to use the handle, it will be told, “Sorry, that handle can no longer be used to access the resource. You’ll just have to close it and get a new handle.”

 

10 comments

Comments are closed.

    • Avatar
      James Forshaw

      Well if you can convince a privileged service to hand you back a handle with more access then you should be able to get by opening the resource then that’s a security vulnerability. Example. Not sure why security researchers are in quotes…

    • Avatar
      Me Gusta

      Think of it this way, what’s to stop an lower privileged application from creating a file in the user’s temp directory and then constantly calling CreateFile on the same file to open new handles to the file?
      It is easy to exhaust handles, it would only become a problem if a process opens a handle and the process can’t be terminated.

      • Avatar
        Stewart

        Handles are a per process resource. If they were limited per process (which they aren’t really) then the attacker process can only exhaust its own handles. And it can do that by calling any function that creates a handle, such as CreateEvent with no name

        • Avatar
          Me Gusta

          OK, first let’s clarify terminology here.
          First, the term handle is used for multiple things, and so let’s cover USER and GDI handles first. These handles are limited to 64Ki per logon session. So one process is able to exhaust another process’ handles.
          Second, kernel handles are not completely process independent either. Think about where the process’ handle table and the objects that the handles refer to reside. Two words, paged pool. Since the paged pool is the kernel shared memory, technically you can indirectly exhaust the total kernel handles that a process can create by exhausting the paged pool.
          Hence it is easy to exhaust handles. But also notice I only stated that this would exhaust handles, I didn’t mention how this would occur.

          • Avatar
            smf

            Is this question completely unrelated to the post? If not, then you’ll have to explain why.

            If it is then there is a per process limit of 18,000 user/gdi handles per process & so one program can’t consume them all. You could spin up more processes, but if you have decided to run a program that is launching a denial of service attack on your machine then I’m sure there are plenty of other interesting things you could do.

          • Avatar
            Me Gusta

            @smf
            There has been a few posts about USER and GDI handles before. Especially on how Windows reacts to them leaking.
            Where Kernel handles are closed as the process closes, User and GDI handles aren’t. They can hang around open and leaked for several minutes before Windows finally closes them.
            Second, GDI handles don’t have an 18,000 limit per process. That limit only applies to User handles. The per process GDI handle limit is the same as the logon session GDI handle limit, so a single process is able to use up all of the GDI handles causing other processes that use the GDI to fail.
            Thirdly, this is only the upper limit on handles. The limiting factor for User and GDI objects is the same as the real limiting factor for Kernel handles. Memory. A GDI handle will point to an object and if this object is a bitmap then you can use this to exhaust the memory for GDI objects really quickly. For example, a bitmap with dimensions 1280×720 would have 921,600 pixels, at 32bpp this would use up ~3.5MiB, you won’t need many of these to exhaust handles indirectly by using up memory.

            So the question may not be completely unrelated, but picking one important point from the OP’s question, the “so few handles” this implies that they were thinking about how many handles that a Windows process is able to reference in general. But the handles were never really the limiting factor, the limiting factor has always been memory, and you can force Windows into a position where it is unable to create handles easily.
            But this all depends on why the original poster thinks this is a security vulnerability. Windows has enough handles to go around for normal use after all.