Static constructors, part four

We'll finish up this series on static constructors by finally getting to the question which motivated me to write the series in the first place: should you use a static constructor like this?

public class Sensitive
{
  static Sensitive()
  {
    VerifyUserHasPermissionToUseThisClass();
  }  
  public static void Dangerous()
  {
    DoSomethingDangerous();
  }
  ...

The intention here is clear. The static constructor is guaranteed to run exactly once, and before any static or instance method. Therefore it will do the authorization check before any dangerous operation in any method. If the user does not have permission to use the class then the class will not even load. If they do, then the expense of the security check is only incurred once per execution, no matter how many methods are called.

If you've read the rest of this series, or anything I've written on security before, you know what I'm going to say: I strongly recommend that you do not use static constructors "off label" in this manner.

First off, as we've seen so far in this series, static constructors are a dangerous place to run fancy code. If they end up delegating any work to other threads then deadlocks can easily result. If they take a long time and are accessed from multiple threads, contention can result. If an exception is thrown out of the static constructor then you have very little ability to recover from the exception and the type will be forever useless in this appdomain.

Second, the security semantics here are deeply troubling. If this code is running on the user's machine then this appears to be a case of the developer not trusting the user. But the .NET security system was designed with the principle that the user is the source of trust decisions. It is the user who must trust the developer, not the other way around! If the user is hostile towards the developer then nothing is stopping them from decompiling the code to IL, removing the static constructor, recompiling, and running the dangerous method without the security check. 1

Moreover, the pattern here assumes that security checks can be performed once and the result is then cached for the lifetime of the appdomain. What if the initial security check fails, but the program was going to impersonate a more trusted user? It might be difficult to ensure that the static constructor does not run until after the impersonation. What if different threads are associated with different users? Now we have a race to see which user's context is used for the security check. What if a user's permissions are revoked while the program is running? The check might be performed while permission is granted, and then the dangerous code runs after it has been revoked.

In short: Static constructors should be used to quickly initialize important static data, and that's pretty much it. The only time that I would use the mechanisms of a static constructor to enforce an invariant would be for a very simple invariant like ensuring that a singleton is lazily initialized, as Jon describes. Complex policy mechanisms like security checks should probably use some other mechanism.


Next time on FAIC: I'm going to join the throngs of tech bloggers who have tried to explain what a monad is.

  1. The resulting program will of course not be strong-named or code-signed by the developer anymore, but who cares?

15 thoughts on “Static constructors, part four

  1. "UserHasPermission" that seems very limitedly scoped and potentially even to runtime changing things.

    If Sensitive requires configuration, like lets say a Encryption Key to fundamentally run that it expects to be in AppSettings["Sensitive/Key"] I find it very good to enforce that validation in the static ctor, and explicitly throw saying "AppSettings["Sensitive/Key"]" is null/invalid/etc.

  2. About the note, some years ago ATI drivers where shipped with the private key in the folder, you could not just decompile, modify and recompile, but also strong-name and code-sign! Didn't check if I could exploit it.

    I never tought about using a static constructor for security checking, but tought about it for checking wheter a library or feature is avaliable or not, in some of those cases I had the impression that the only way for checking is to catch the TypeInitializationException.

  3. Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1298

  4. I suddenly understood monads when I viewed them as just a convention. Previously I always thought "what is it good for?". Answer: They are just a convention or a kind of interface used to allow Haskell to offer do-syntax. They really are little more than that.

    If I'm wrong with that I'm eager to be rectified.

    I also found a particular analogy useful: Monads are just abstract structures like groups and rings are. Just another kind of abstraction for just another pattern.

    • Your first tack -- that "monad" is just another design pattern for types, like "singleton" -- is the tack I'm going to take. Your second tack -- that a monad is just an abstract structure, specifically a monoid in the category of endofunctors -- is the tack I am going to explicitly avoid.

  5. So you've been waiting with this to use it now to boost the popularity of your new web site? Clever! :D

    [That was a joke, of course. :) I'm really curious now, considering your knack for explaining things in a way that even I can understand. :) ]

  6. I was disappointed to find that the EWS Managed API code samples (at least the ones I've looked at so far) are littered with just this kind of abuse of static constructors.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>