Security and Asynchrony


In a
comment to my last ramble, about asynchronous execution and pinning, someone
asked for advice on using Windows impersonation in a managed application. style="mso-spacerun: yes">  Unfortunately, the managed platform
currently has poor abstractions and infrastructure for controlling Windows
identity, and indeed for most of the unmanaged Windows security system. style="mso-spacerun: yes">  For example, the managed classes for
WaitHandles and Streams lack overloads for specifying SECURITY_ATTRIBUTES. style="mso-spacerun: yes">  It’s true that we have defined some
classes like System.Security.Principal.WindowsIdentity and WindowsPrincipal, but
I don’t think these classes add enough value in their current form.


"urn:schemas-microsoft-com:office:office" /> size=2> 


For now,
you might even decide to avoid the managed abstractions and PInvoke to the
underlying OS services, like RevertToSelf and SetThreadToken. style="mso-spacerun: yes">  Be aware that this technique won’t work
well in a fiber-based environment like SQL Server. style="mso-spacerun: yes">  In that world, a logical thread (i.e.
fiber) might be switched to a different operating system thread after you
PInvoke to initiate impersonation. 
If a fiber switch does indeed happen at that time, a different logical
thread will now execute inside your impersonated context.


size=2> 


We
certainly understand the limitations of our current support and we’re working to
provide better abstractions in a future release.


size=2> 


The
reason for this poor support is perhaps obvious. style="mso-spacerun: yes">  In managed code, the focus of security
is Code Access Security, not operating system concepts like impersonation. style="mso-spacerun: yes">  We put a lot of effort into capturing
CAS state and propagating it automatically through asynchronous operations. style="mso-spacerun: yes">  For example, if Thread 1 creates and
starts a new managed Thread 2, the CAS stack information from Thread 1 is
automatically captured and propagated to the base of Thread 2. style="mso-spacerun: yes">  When you call the normal ThreadPool
operations, a similar capture and propagation of stack evidence occurs. style="mso-spacerun: yes">  A sophisticated and trusted client of
the ThreadPool can trade off that implicit security for better performance, by
using ‘unsafe’ operations like UnsafeQueueUserWorkItem and
UnsafeWaitForSingleObject.  A
similarly sophisticated and trusted client could PInvoke to CreateThread, to
avoid attaching his CAS information to the new thread.


size=2> 


Why do
we propagate the CAS information from one thread to another in this
manner?


size=2> 


Well,
System.Environment.Exit() can be used to terminate the current process. style="mso-spacerun: yes">  This is considered a privileged
operation, so it’s protected by a Demand for UnmanagedCodePermission. style="mso-spacerun: yes">  (I can’t remember if it’s a FullDemand
or a LinkDemand.  For the purposes
of this blog, let’s pretend it’s a FullDemand). style="mso-spacerun: yes">  Because of the demand, partially trusted
code cannot call this API directly. 
If it does attempt the call, the security system will examine the stack
and discover that partially trusted code is involved in the operation. style="mso-spacerun: yes">  A security exception will be
thrown.


size=2> 


But what
if the partially trusted code can find a delegate declaration with the same
signature as Exit()?  There are
plenty of fully trusted delegate declarations, like
System.Threading.ThreadStart.  (I
realize that ThreadStart doesn’t have quite the right signature for
Environment.Exit, but you get the idea). 
If the partially trusted code can form one of these delegates over the
Exit() method and then queue the delegate for execution on the threadpool, it
can mount a security attack.  That’s
because a threadpool thread will now call Exit() and satisfy the security
Demand.  An examination of the stack
would not find any code from the partially trusted attacker.


size=2> 


We
prevent this attack by capturing the stack of the partially trusted caller when
he calls ThreadPool.QueueUserWorkItem. 
Then when the stack crawl is initiated by the Demand on Exit(), we
consider that captured stack.  We
discover the partially trusted code on the captured stack and fail the
Demand.


size=2> 


In
addition to creating a thread or initiating a safe ThreadPool operation, we also
capture and propagate CAS information in services like
System.Windows.Forms.Control.BeginInvoke.


size=2> 


However,
we do not capture and propagate CAS information for the most common asynchronous
operation of them all – finalization. 
I can give two reasons to rationalize this fact.


size=2> 



  1. style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"> face=Tahoma size=2>Finalization is intended for cleaning up resources rather
    than for arbitrary execution.  The
    body of the finalize method should be self-contained; it should be designed so
    it is not subject to re-purposing attacks. style="mso-spacerun: yes">  For example, fully trusted code should
    never expose an object that will call through an arbitrary delegate from its
    Finalize() method.

size=2> 



  1. style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"> face=Tahoma size=2>The performance impact of capturing and propagating stack
    information on each finalizable object would be unacceptable. style="mso-spacerun: yes">  It’s an unfortunate fact of life that
    performance and security are often at odds with each other. style="mso-spacerun: yes">  The best we can hope for is to strike
    an appropriate balance between these competing needs.

size=2> 


What if
you need to do something delicate in a Finalize() method? style="mso-spacerun: yes">  More generally, what if you are building
your own ThreadPool or your own queue of server requests? style="mso-spacerun: yes">  (Of course, there are many good reasons
for using our ThreadPool rather than writing your own, but let’s ignore this for
a moment).  Ultimately, any array of
objects that’s shared between two threads can be a scenario where the thread
inserting into the array might need to propagate its CAS information to the
thread that is removing objects from that array and operating on
them.


size=2> 


The
solution to this problem is for you to call
System.Threading.Thread.GetCompressedStack() and SetCompressedStack()
yourself.  Of course, you need to
have a high level of privilege (probably ControlEvidence) before you can do
this.  These APIs were not public in
our V1 release, but they are publicly available in 1.1.


size=2> 


If you
go this route, there is one important detail you should be aware of. style="mso-spacerun: yes">  The current behavior of this API is to
place the “attached” CAS compressed stack at the base of the target thread. style="mso-spacerun: yes">  It is not inserted into the new thread’s
stack at the current stack location. 
For normal stack crawls, this detail won’t matter. style="mso-spacerun: yes">  But if your stacks contain combinations
of Deny, Assert and PermitOnly statements, then position is significant. style="mso-spacerun: yes">  By considering these statements out of
order – which is our current behavior – it’s theoretically possible to get
different results.


size=2> 


For
example, you might Assert and then pick up a request with a compressed stack
which you install with a SetCompressedStack. style="mso-spacerun: yes">  This is a questionable practice already,
because you really shouldn’t execute arbitrary code inside the scope of an
Assert.  You should try to contain
the scope of an Assert as much as possible. style="mso-spacerun: yes">  Along the same lines, I’m personally
uncomfortable with applications that base their security on Deny or PermitOnly
statements.  Such statements can
always be trumped by a subsequent Assert.


size=2> 


Anyway,
transferring a compressed stack is generally much more secure than not
transferring the compressed stack. 
So any concerns about subtle interactions with Assert, Deny &
PermitOnly based on the order in which we consider the current thread’s stack
and the transferred stack are secondary.


size=2> 


size=2>Incidentally, Get/SetCompressedStack has a nifty merging mechanism which
can avoid some common overflow scenarios. 
Imagine what happens if you queue an asynchronous read. style="mso-spacerun: yes">  The API you call will capture your
compressed stack and flow it through the threadpool. style="mso-spacerun: yes">  When the threadpool uses one of its
threads to call your completion, the caller’s CAS information is available as we
have seen.  A perfectly reasonable
thing to do in the completion callback is to initiate a new asynchronous read,
and return.  Now rinse and repeat
indefinitely.


size=2> 


In terms
of the stacks of the operating system threads, they are all nicely unwound by
virtue of performing asynchronous operations. style="mso-spacerun: yes">  But in terms of the CAS compressed
stacks, their growth is unbounded. 
When we initiate the 1000’th asynchronous read, the prior 999 stacks are
all being propagated along.  What
makes this particularly painful is that at least 998 of those compressed stacks
are completely identical!  The
additional stacks typically convey no new security information.


size=2> 


size=2>Fortunately, the mechanism for capturing and merging compressed stacks
contains a simple pattern recognizer. 
In this sort of scenario, the pattern recognizer will discard any obvious
redundancies.  The CAS information
quickly finds a fixed point.


size=2> 


I’ve
already pointed out that there’s a spectrum of asynchronous operations. style="mso-spacerun: yes">  At one end of the spectrum, we have
obviously asynchronous scenarios like Stream.BeginRead,
ThreadPool.QueueUserWorkItem, Thread.Start and raising Events. style="mso-spacerun: yes">  At the other end of the spectrum, we
have subtly asynchronous scenarios like one thread calling through an object
that was placed into a shared static reference by another thread. style="mso-spacerun: yes">  Ultimately, if you have shared memory
and multiple threads, you have the potential for asynchrony and security
attacks.


size=2> 


This is
troubling, because there isn’t a bright line between risky operations that need
securing via techniques like transferred compressed stacks versus normal safe
operations which don’t warrant the overhead of stack transfers.


size=2> 


One
scenario that’s particularly troubling to our team is events. style="mso-spacerun: yes">  What if we can find an event that’s
raised by some fully trusted code with a signature that matches
System.Environment.Exit()?  Well, we
could wire up the fully trusted caller (the event source) to the fully trusted
but dangerous Exit service (the event sink) using a fully trusted delegate of
the appropriate signature.


size=2> 


At that
point, we just need to wait for the event to fire and the process will
terminate.  There is no partially
trusted code on the stack.


size=2> 


We’ve
discussed many ways to solve this problem. 
Most of them have a clumsy programming model. style="mso-spacerun: yes">  All of them have a significant
performance impact.  None of them do
a great job of solving all the attacks possible with indirect calls (i.e.
non-Event usage of delegates, and indirections through well-known interface
methods or virtual methods).


size=2> 


Indeed,
Events are probably the least susceptible to attack of all the indirect call
attacks.  That’s because almost all
Events on our platform share an idiosyncratic signature of (Object sender,
EventArgs args).  An attacker isn’t
going to find a lot of powerful APIs like Exit that have the same
signature.  Indeed, checking for
dangerous methods with this sort of signature is just one of the many, many
security audits that we perform throughout our frameworks before shipping a
release.


size=2> 


Still,
it’s definitely an area where we would like to do better, and where we shall
continue to invest design effort.

Comments (5)

  1. I’m not very CAS savvy, but I have a question. When a method is bound to a delegate LinkDemand permissions are evaluated, but a full demand isn’t. Can you explain why that is? Wouldn’t doing this eliminate a large part of the problem? Of course, any non declarative security demands still wouldn’t be handled.

  2. Chris Brumme says:

    I would like to apologize for a glaring error in the above blog. I write these things when I don’t have access to the Internet or a source tree (like at airports), so I largely rely on memory.

    I didn’t look closely enough at Thread.Get/SetCompressedStack. It turns out that we have a StrongNameIdentityPermission on those APIs which restricts usage. I had imagined that these APIs would only have a demand for ControlEvidence.

    So you will have a hard time building your own secure asynchronous mechanisms the way I described, unless you are riding on top of services that flow CAS information like the ThreadPool operations.

    I’ll try to be more rigorous next time.

  3. Dmitriy Zaslavskiy says:

    Chris could you comment on the following
    Thread.CurrentPrincipal. copied to when you do:
    Thread.Start()
    Delegate.BeginInvoke()

    and NOT
    ThreadPool.QueueUserWorkItem()
    new System.Threading.Timer()

  4. Chris Brumme says:

    Dmitriy,

    The only comment I can make is that there is no consistency in how we treated a number of contextual attributes like the security principal, the call context, the culture, etc. As I mentioned in the blog, we did a pretty good job of treating CAS information consistently.

    We’ve been revisiting this broad lack of consistency, and we’ve been exploring ways we can guarantee better consistency going forwards. I really like where we’re now headed, but I can’t comment further since it’s going to be some time before we ship a product that addresses the flaws that you are remarking on.

    Until then, this is one of those rough edges that you will have to live with.

  5. Chris Brumme says:

    Jeroen,

    You ask why we check the LinkDemand when a delegate is created, but we don’t check a FullDemand. We definitely need to check the LinkDemand, because it will disappear otherwise. The Delegate’s Invoke method will be the immediate caller to the protected method. So if we didn’t check the LinkDemand, I could trivial bypass all LinkDemands by finding a trusted Delegate with the correct signature and then calling through that.

    In the case of a FullDemand, it will still be checked. The only risk is that partially trusted code can eliminate itself from the stack, as I described. Consider the typical case where a bunch of public overrides all delegate to a single private worker method. In that scenario, the FullDemand will be placed on the private worker method, but the Delegate will be formed over one of the public overrides. In this case, your suggestion still won’t check the FullDemand when the delegate is constructed.

    It’s a good idea that perhaps increases security under certain circumstances. But it’s an unappealing solution because suddenly permission checks will be skipped when I trivially change the implementation of the target.

Skip to main content