Friday, March 11, 2005

I Hate Strong Names

Don't build strongly named assemblies unless you have to.


I've thought for a long time now that strongly named assemblies cause more trouble than they're worth (in most scenarios). This was brought home to me late last night at work as I struggled to recover from an ill-planned upgrade of NAnt. Basically, I got screwed by the fact that the version of NDoc that ships with NAnt is identical to the one I could download, but because they were built on different days, they had different build numbers, and that lead to some assembly load failures. OK, so that's more an argument for “don't base your build number on the date”, but that's another post. The experience still served to reinforce my low opinion of strongly-named assemblies.


See, the problem is that the whole strong-naming thing is supposed to give us two features:



  1. Tamper detection: If someone mucks with your assembly, the runtime will detect it because it's “signed“.

  2. Version policy: A strong name means that only exactly the version you request will satisfy a load attempt.

But of course, to do the first one right, you really need to have a whole PKI infrastructure in place, not to mention keeping private keys under tight control. And what do you do if it's an open source project? Even if you solve that problem, I still look at it and say, “If someone has the ability to mess with the bits in some DLL on my hard drive, I probably have bigger problems.”


I think point number two suffers from a severe case of over-engineering. Or, if you like, the issue is that the problem is just inherently very, very hard. Because the system has no way of inferring anything about whether two different versions are compatible or not, it has to assume that they never are. Which leads to problems like the one I had where NDoc.Core 1.3.1856.0 and NDoc.Core 1.3.1811.0 don't talk to each other. Of course, there are hooks like the <assemblyBinding> element in the config file (which I wound up using), but the rules are complex, and the file is hard to get right. Oh, and it fails silently if you get it wrong. The mere fact that the assembly loading and versioning versioning talk I used to give was three hours long should tell you something.


Having spent more and more of my time over the last few years writing real systems, I'm convinced that the simple approach to assembly loading is usually best: don't use strong names. Just put all your DLLs and EXEs in one directory, and the right thing will happen. In the rare event that you do need to make a breaking change to an assembly and you aren't recompiling the whole application, just change the name, like foo2.dll. Some may object that it's less aesthetically pleasing, but I think developers do a lot of stupid stuff in the name of making things prettier.


Will this approach always work? Of course not. BizTalk is one place where you're pretty well constrained to deploy to the GAC, and that means using strong names. Fine - not your choice, and there may well be a good reason for them making you do it that way. But I'd be willing to bet that 99% of the code out there isn't being deployed somewhere where strong names are a requirement. So make your life simple(r) and ditch strong names.


There - I feel better now. :)

36 comments:

  1. Hi Craig.



    Hmm. I like strong names. But I think they are no security related thing ;-)



    http://staff.newtelligence.net/michaelw/PermaLink.aspx?guid=c453c508-e892-4371-b9ae-ffec9ae8489e

    ReplyDelete
  2. Hmm, your blog entry points out the theoretical advantages, but I don't see any rebuttal to my objections on practical grounds. That is, while those features are nice, they turn out not to provide more trouble than value.



    Is there some general scenario wherein you need a globally unique identifier for an assembly, as opposed to the locally unique one we've already got - the simple name?

    ReplyDelete
  3. But I should say I do realize that your entry is really about how the "signatures" don't provide security - on that part we totally agree.

    ReplyDelete
  4. The version policy in Whidbey and later will support servicing, which means that only major and minor version numbers are compared and not the build numbers.

    ReplyDelete
  5. I think the only real scenario where strong names are not more trouble than they are worth is when you are developing smart client apps for desktops.



    Using strong names in this scenario gives you a couple of things that are really valuable. First, you can assign very specific security permissions for only the assemblies that have your strong name (admins love this one) and thus avoid having to open a very wide security hole. Second, you get the protection of knowing that no clever hack on the client end could use (or attempt to use) your assemblies for anything they weren't designed to do. Versioning and such can be handled by publisher policy assemblies.



    For everything server-side, I agree...Strong names are a pain!

    ReplyDelete
  6. Actually, regarding point number one, even if you have a PKI infrastructure in place, there is one lynch pin requirement that must be in place: everything that _uses_ the assembly must implement a code access security policy (be it within code groups or within calling assemblies in code) to explicitly validate the key that the strong named assembly was signed with. It is invalid to assume that because an assembly is strong named and because it is verified to be "untouched" that it truely contains the information the developer placed in it. I can very easily spoof a strong named assembly, assuming the public key evidence isn't being matched against a known valid value.



    Consider this scenario:

    A strong named assembly exists on the web for download. I download the assembly, rip out the MSIL, make a change (or 10), re-build the assembly signing it with a newly generated public/private key combo, hack the website and replace the existing assembly with mine. Will the verification of the assembly succeed when it is accessed by a client? You bet it will, because the hash of the assembly matches perfectly. And unless a client explicitly validates which key was used in the strong naming, they are none the wiser.



    All that to say, strong naming does provide excellent versioning, and CAN be used to validate the source of an assembly, assuming that you match the key information against a known value. But that is still no guarantee that the private key of the developer hasn't been compromised. :)

    ReplyDelete
  7. Wesner: but will the default policy be the same as now? Because if you have to mess with a config file, it's really no better than what we have at the moment. You can already redirect a range of versions to effectively ignore build and revision. But what's the point? Without strong names, I can simply overwrite the DLL - boom, versioning, and no weirdness to remember.



    I do hope that Whidbey gets it right, but I'm skeptical that they can.

    ReplyDelete
  8. I think where they blew it is enforcing version policy for privately deployed strong-name assemblies. It all makes perfect sense for GAC deployed assemblies, where you can have multiple versions installed simultaneously. For private assemblies, you can't, so there isn't much point in enforcing it - all you're doing is breaking the app until they define binding redirects or install publisher policy. Plus, since publisher policy goes in the GAC, you break XCOPY deployment. Lame.



    The problem is that for component providers (people providing class libraries, controls, etc), when you don't strong name your assemblies you're limiting what your customers can do. If I ship a class library that isn't strong named, then I'm preventing my customers from strong-name signing their assemblies. So now they can't put their code in the GAC, use it in COM interop scenarios, etc. It's a real drag.

    ReplyDelete
  9. Agreed - if you're a library vendor, it's a somewhat different story. I would argue that that's not most software.



    Also, often times limiting what your customers can do is a good thing. Not always, but sometimes. There's a common tendency towards "Avoiding decisions by providing options"...vendors should think hard about whether or not COM interop, etc. is an important scenario.



    Primarily what I'm asking is that people not making strong naming their default mode of operation.

    ReplyDelete
  10. It's true that most software isn't libraries. That is, however, the exact scenario that you're encountering, so it's appropriate for the discussion.



    Does ndoc.core need to be strong-name signed? Probably not, in this particular case. But for a library vendor, limiting what your customer can do can directly translate into lost revenue. All it takes is one customer insisting that they be able to install their assembly into the GAC...



    I think the problem needs to be solved on the framework side. Hopefully Whidbey will address this, but I haven't checked yet.











    ReplyDelete
  11. The NDoc situation merely served to remind me of the problem. And as you said, even there the choice was questionable. The general scenario - perhaps as much as 95% of the time - it sounds like we don't disagree on: don't use them.



    If we want to segue into discussions of customer pressure, then I would like to challenge your statement about "all it takes is one customer". I think that if you're making decisions based on the demands of one customer, then you either have very few customers, or your product is going to be a mishmash of poorly thought-out features, as you'll be coding to meet the needs of every stray thought that goes through the head of your users. Just have a look at the list of feature requests on most open-source projects. Many of them don't make sense, and many are clearly giant changes to the way the product works. The same is true for closed source, in my experience.



    Now, I agree that sometimes all it takes is one customer because management is making bad decisions in deciding to apply grease to the squeaky wheel. But that doesn't mean it's what *should* happen.



    Which is not to say that providing features (e.g. a strongly named assembly) based on customer demand is always a bad idea - far from it. Just that, as usual, the truth lies between the extremes. Hence my original assertion that strong names should neither be always used nor never used - merely that they should be used less often than they are now. On which, it seems, we agree.

    ReplyDelete
  12. Make sure you give Rich (http://hoser.lander.ca/) an earful about this if you get a chance... I'm still trying to convince him that Java's classpath-based loading, while leads to a bit of path hell, is less hellish than our current loading/binding/deployment situation... :)

    ReplyDelete
  13. Perhaps when I said "all it takes is one customer" I should have said "all it takes is one sufficiently important customer". When several million dollars are on the line (as they frequently are in the business in which I work), management may be right to apply that grease, even if it isn't the best technical decision.



    My original point was simply that "unless you need to" isn't necessarily an easy thing to determine, since you are affecting more than just yourself when you make that decision about your library.



    Ultimately, I think it's a flaw in the framework that I hope will be addressed.



    Whew - being in violent agreement with you is exhausting, man. ;)

    ReplyDelete
  14. Ha ha ha. Yeah, whenever I see a comment from Kevin Dente, I always know my feet are about to be held to the fire. :)



    On the one customer point: conceeded.



    On it not being easy to determine: also agreed.

    ReplyDelete
  15. Dang, I never intended to get the reputation of being a PITA. I'm a big fan of your blog, and have learned a lot from it.

    ReplyDelete
  16. Actually, I meant that in a very positive way. I like having to defend my statements from rational objections - that's a great way for me to learn. Don't change a thing.

    ReplyDelete
  17. @Wesner Moise



    The claim is not correct. Whidbey works the same way as v1.0. The feature you are talking about will happen in Orcas, the release after Whidbey.



    Craig,



    Your observation is well noticed. Yes, if you are component vendor, you have to think about strong name and versioning.



    Kevin,



    That is a good suggestion. I'll keep it in mind.



    It is hard to say if this is a flaw or not. If the binding behavior is different in GAC than in app base, people will be more confused.



    But we will work on this problem. If you have idea, send me email via the contact link on my blog.

    ReplyDelete
  18. Jenfeng - thanks for stopping by. ;)



    IMO, if people can understand the way version policy works now, I don't think the variation that Kevin suggests is going to throw them...it's damn complicated already.

    ReplyDelete
  19. I think the key issue is that with the current model, the library provider implicitly dictates the version binding policy. By choosing to strong name sign or not, the library is controlling whether the client uses "tight" version binding. That just seems wrong to me. The client should determine whether they want to couple to a specific version or not, it shouldn't be dictated to them by the library.



    ReplyDelete
  20. Yeah, I think you've hit it right on the head.

    ReplyDelete
  21. It is not really complicated, it is just inconvenient.



    Both client and library provider can dictates the version binding policy. Client through app.config and library provider through publisher policy. Actually, this is where the complexity comes in --- too many binding policies.



    We enforce the strict version binding for strongly named assemblies, because, by definition, strongly named assemlies with different version numbers are different.

    ReplyDelete
  22. All due respect Junfeng, but when I used to teach for DevelopMentor, assembly binding and versioning was one of the most topics in the class, and one that students had the hardest time with. Publisher policy in particular. I think *I* understand it well enough, but I got paid to read documentation and explore how it works. That's not really the case for most developers.



    I certainly understand why you made the decision that two different versions are considered incompatible by default. What else could you possibly do? But that doesn't change the fact that versioning sucks, and that for me, it's always been easier to manage versioning independently of what the CLR does for me.

    ReplyDelete
  23. I think Strong Names assemblies are very useful for people who need them; and those people need to develop the appropriate processes.



    General guidance for most developers should 'obviously' be not to use them.

    ReplyDelete
  24. Just say no to DLL hell.



    How hard can it be to determine? ;)

    If you have millions of dollars worth of customer that wants it, then you give it to them.



    I think discussions show that the current solution is not the best.



    Remember this? It confused me: http://weblogs.asp.net/rmclaws/archive/2003/12/23/StrongNameKeys.aspx

    ReplyDelete
  25. Versioning policy in .Net has been very confusing to me from the very beginning. One one hand we were told to "put all the assemblies" in the private bin of the app... OK... but now I have component A that relies on v1.0.100.1002 of library A and component B that relies on library A but a newer bug-fixed version: v1.0.101.1050. Not to mention the assembly name clashing, it produced situations that didnt make sense, and could only be solved through the policy binding changes that are difficult to wade through as it is.



    Personally, I will always feel that the framework should honor up to the FIRST 3 numbers, and somehow WARN/LOG when not binding directly to the expected version but finding a newer "bug-fixed" one instead.

    ReplyDelete
  26. See my post in Platform vs Library Versioning (http://wesnerm.blogs.com/net_undocumented/2004/06/platform_vs_lib.html).



    Jeffrey Richter indicated that config files were not very usable, even for internal Microsoft developers. So, the plan, either for Whidbey or for Orcas/Longhorn (not sure), is that servicing will be the default behavior, without the need to modify a config file.

    ReplyDelete
  27. Thanks Wesner - I'd read your post when you first wrote it, but forgot about it until now.



    My thoughts after reading it again are basically, "That sounds even more complicated." and "I'm not sure if that will make it better or worse."



    I'll have to think about it for a while.

    ReplyDelete
  28. Wesner,



    The work will be done in Orcas.



    Jeffrey Richter has an article on the future of assembly versioning



    http://www.theserverside.net/articles/showarticle.tss?id=AssemblyVersioning



    It was the thinking at that time frame. Things will undoubtfully change. But you can have a rough idea on how we will move forward.



    Craig,



    You probably will get what you want. And with more new stuff you may or may not want. It sucks. But that is how we move along.



    The current policy system does have its problem. I constantly hear complain about it. We will work on it. Just not in Whidbey. It is too close to ship.

    ReplyDelete
  29. Junfeng: thanks.



    BTW, the CLR rocks hard overall. I have to look hard to find things to complain about. Keep up the good work. ;)

    ReplyDelete
  30. Interesting finds this week

    ReplyDelete
  31. It seems to me that one fairly small change could make all this a lot easier.



    How about if the version number was not checked for strong-named assemblies that are not in the GAC?



    If the assembly is not in the GAC, but only in the local folder, surely the better assumption would be that we _do_ want to use it.



    Lets consider - if the wrong version is in the local folder there are two possibilities:

    1. It is compatible and everything works.

    2. It is not compatible and something breaks.



    But - by causing a load error if the version is different, you reduce that down to:

    1. Whether it is compatible or not, everything breaks.



    This could be controlled by a config option, so that you could turn it off if you don't like it, but it seems it would cause the code to work most of the time with no down-side?

    ReplyDelete
  32. The counterargument is a security-based one: if there are 30 copies of an assembly on a machine, and it turns out there's a bug, the GAC is the place where you go to update all of them and eliminate the vulnerability.



    Of course, if you deploy privately with no strong name, you get to decide what "compatible" means. :)

    ReplyDelete
  33. But surely that's the point - if I deploy privately why don't I get to decide what "compatible" means in all cases, strong name or no strong name.



    I'm not arguing against the GAC or strong names, but when the assembly is not in the GAC then surely the rules can change. Security is taken care of by the key signing, versions should have nothing to do with security.



    The argument that you shouldn't have 30 copies of an assembly on a machine is another issue entirely. But given that strong named assemblies are allowed outside of the GAC - versioning needs to work differently because the versioning requirements outside of the GAC are different.

    ReplyDelete
  34. As it stands right now, you *can* control what your application loads. Just deploy locally under a different version number and use a binding redirect in your exe's config file. Is there some value over that to what you suggest? Or is it just slightly more convenient?



    As for signing providing "security", I don't buy it. If they can tamper with a DLL in your app directory, they can tamper with the EXE, and you're screwed. The tamper detection aspects of strong names are almost completely worthlesss IMO.

    ReplyDelete
  35. I think we agree on all points



    1. Definitely it's about convenience - why should the _default_ behaviour break perfectly good code and require me to edit a config file every time I drop a new copy of a DLL in the folder.



    2. I agree that signing doesn't provide a great deal of security, but my point is that whatever it does provide has nothing to do with versioning.



    I would suggest - versioning is there to support multiple, shared versions in the GAC, once you are deploying to the local folder there are no versioning requirements - certainly not until the OS prevents me from overwriting a file in the local folder (based on version).



    Go back to your original post - my solution would solve the problem 100%, without any drawbacks.

    ReplyDelete
  36. Right. I think we do agree: your method is a more convenient way of doing what I did with a binding redirect.



    So given that *unsigned* assemblies already exhibit the behavior you like, I take it that you (like me) try not to sign unless you absolutely have to? And that you prefer unsigned assemblies?

    ReplyDelete