Unification Policy

The CLR’s strict versioning binding policy for strongly-named assemblies was the first step towards alleviating the “DLL-hell” problems of the past. Whenever you link to a strongly-named assembly, the exact identity of the referenced assembly is recorded in your assembly’s metadata, and Fusion will bind to exactly the same version at runtime (binding policy aside). This ensures that the components the developer tested at design-time is exactly what the user will use a run-time.

Strict binding policy, however, is only a very rudimentary way of enforcing that the assemblies used at run-time match a given developer’s intentions. Often times, an application or component developer will create a set of assemblies designed to work in conjunction with one another. These assemblies are shipped together as a suite, and the version together as a whole. In a future release of this set of assemblies, new versions of all the components are developed, and are intended to work with the matching versions of other assemblies in that set. The .NET frameworks itself is an excellent example of a stack of related assemblies. There is a v1.0 stack of FX assemblies, and a v1.1 stack of FX assemblies, and the assemblies which comprise the FX stack are designed to only work with other assemblies in that same version of the stack.

Even though the assemblies in the group certainly will link against the exact version of the other assemblies in that set, this relationship by itself is not enough to guarantee that these assemblies will run together as a coherent stack. This guarantee is often desired by the assembly developer because these assemblies are tested together, and not designed to run against other versions. Binding policy at the app or administrator level can be used to violate the guarantees intended by the app developer.

A related problem is that even though strongly-named assemblies (either alone, or within a group) can be stored and loaded side-by-side, it may not always be desirable to actually have two (or more) copies of a strongly-named assembly loaded concurrently in a given scope (appdomain / process / etc). Whether a component is truly side-by-side capable at runtime depends on how the component is written. How can a component developer ship different versions of an assembly that is not run-time side-by-side capable, and guarantee that only one version of that component will be loaded at run-time?

The .NET framework assemblies have both of these problems. During development of the v1.0 product, the CLR / FX test teams explicitly tested the v1.0 FX stack against the v1.0 CLR. The same testing occurred for the v1.1 CLR and the v1.1 FX stack. Side-by-side testing naturally results in an explosive matrix of test cases, and because of this, there was no explicit testing done for mixing and matching v1.0 and v1.1 frameworks assemblies. Furthermore, there are some FX assemblies (I believe winforms is one such example) that are not designed to be run-time side-by-side capable.

A generalized solution for addressing the problems above is still not yet available. In the interim, a CLR v1.1 feature known as unification policy was developed that addresses these problems (albeit in a very limited way). Unification is a form of binding policy which occurs after application policy is evaluated, but before publisher policy is applied. A hard-coded table of assemblies which shipped in the v1.1 CLR product is consulted and references to any version of assemblies in that list are automatically redirected to the version of the assembly that shipped in the v1.1 product.

Through unification policy, it is possible for a v1.0 application to be configured to run against the v1.1 CLR (via the <requiredRuntime> or <supportedRuntime> configuration tags), and automatically have the references to the v1.0 FX stack redirected to the appropriate versions for the v1.1 CLR, with no re-compilation, or manual authoring of binding redirects by the developer or end-user. Similarly, an app built against the v1.1 CLR can utilize shared components written for the v1.0 CLR, and again the v1.0 FX references will automatically be redirect to the v1.1 stack. Through unification policy, the v1.1 process will always use v1.1 FX assemblies, and there will not be any mix/match conditions which could cause problems at run-time.
Unification policy is applied before publisher policy is applied in order to allow for servicing for specific assemblies in the frameworks stack. In the event a security fix for a particular assembly is deployed along with publisher policy for that assembly, we did not want unification policy to interfere with the intended servicing.

Another scenario which was considered in the design for unification policy is that of “speedboat” assemblies. Speedboat assemblies are updates to assemblies which shipped in a given FX stack, with new features, that ship before a new FX stack is shipped with a whole new version of the CLR. Speedboats are designed to be used optionally by app developers on an as needed basis. Since speedboat assemblies are designed to be used per-application, it is not appropriate to ship a publisher policy assembly for speedboats, because publisher policy impacts the entire machine. In order to allow speedboat assemblies to be consumed, a by-pass mechanism was engineered into unification policy. If a <bindingRedirect> statement for an assembly in the FX assembly list is found in the app.exe.config file, unification policy for that assembly will be skipped.

Because binding redirect statements can be used to by-pass unification rules, it is possible for a malicious app author to force loading v1.0 and v1.1 assemblies at the same time through app.exe.config. For this reason, binding redirects are controlled by a security permission, which is not available by default under partial trust.

It turns out that the decision to disable binding redirects from partial trust actually ended up hurting no-touch deployment scenarios. When servicing a given application, developers are able to simply modify the application configuration file to specify the binding redirect needed to consume a new version of a strongly-named assembly. The binding redirect saves the developer from having to recompile all applications which referenced the old version of the strongly-named assembly. Servicing zero-touch applications using this technique is not possible because the binding redirect permission is not granted in partial trust.

For more information about unification, and side-by-side execution of the CLR, you can refer to this MSDN article.

Skip to main content