Spot the Defect: Modifying the Security Policy in Code


Modifying the CLR’s security policy can be done in your code by interacting with the SecurityManager object.  Specifically, you can access the PolicyHierarchy method which will expose an enumerator over the policy levels, and the SavePolicy method, which will commit your changes to the policy.  However, using these methods can be counter-intuitive sometimes.  Here’s a sample method with a common problem in it — if you can spot the defect, leave a comment, I’ll provide answers tomorrow.

For some hints, the problem is not:

  • The policy changes won’t affect the running code
  • If the policy is not setup close to the default, the method may not work

Here’s the code:

 1  /// <summary>
 2  ///     Modify the security policy to give an assembly FullTrust from the LocalIntranet
 3  /// </summary>
 4  /// <remarks>
 5  ///     This method modifies the machine policy level and adds a code group that
 6  ///     matches the given application’s key and name but does not take into account
 7  ///     version information.
 8  ///
 9  ///     If the policy is set up in a way that is different from the default CLR security policy,
10  ///     this method may not produce the intended results. Assuming a default security policy, the
11  ///     goal of this method is to produce a policy that looks like this:
12  ///
13  ///     Machine level:
14  ///       1. All code: Nothing
15  ///         1.1 Zone – MyComputer: FullTrust
16  ///           1.1.1 StrongName – MS Key: FullTrust
17  ///           1.1.2 StrongName – ECMA Key: FullTrust
18  ///         1.2 Zone – Intranet: LocalIntranet
19  ///           1.2.1 All code: Same site Web
20  ///           1.2.2 All code: Same directory FileIO
21  ///           1.2.3 StrongName – appKey, assemblyName: FullTrust
22  ///         1.3 Zone – Internet: Internet
23  ///           1.3.1 – All code: Same site Web
24  ///         1.4 Zone – Untrusted: Nothing
25  ///         1.5 Zone – Trusted: Internet
26  ///           1.5.1 All code: Same site Web
27  /// </remarks>
28  /// <param name=“assemblyName”>assembly to grant FullTrust form the Intranet</param>
29  /// <param name=“appKey”>Key of the assembly</param>
30  /// <exception cref=“ArgumentNullException”>
31  ///     If <param name=“assemblyName”/> or <param name=“appKey”/> are null
32  /// </exception>
33  /// <exception cref=“ArgumentOutOfRangeException”>
34  ///     If <param name=“assemblyName”/> is empty
35  /// </exception>
36  /// <returns>true if the policy was modified, false otherwise</returns>
37  public static bool TrustIntranetAssembly(string assemblyName, StrongNamePublicKeyBlob appKey)
38  {
39    Debug.Assert(assemblyName != null && assemblyName != String.Empty, “assemblyName cannot be empty”);
40    Debug.Assert(appKey != null, “appKey cannot be null”);
41
42    if(assemblyName == null)
43      throw new ArgumentNullException(“assemblyName”);
44    if(assemblyName == String.Empty)
45      throw new ArgumentOutOfRangeException(“assemblyName”, assemblyName, “assemblyName cannot be empty”);
46    if(appKey == null)
47      throw new ArgumentNullException(“appKey”);
48
49    bool addedGroup = false;
50
51    IEnumerator policyEnumerator = SecurityManager.PolicyHierarchy();
52
53    // iterate over each policy level until we find the machine level group
54    while(policyEnumerator.MoveNext())
55    {
56      PolicyLevel policyLevel = policyEnumerator.Current as PolicyLevel;
57      Debug.Assert(policyLevel != null, “Unexpected object in policy enumerator”);
58
59      if(policyLevel.Label.Equals(“Machine”))
60      {
61        // get a list of all the code groups on the machine level
62        CodeGroup root = policyLevel.RootCodeGroup;
63        IList children = root.Children;
64
65        // iterate over each child until we find the LocalIntranet zone
66        for(int i = 0; i < children.Count; i++)
67        {
68          CodeGroup currentGroup = children[i] as CodeGroup;
69          Debug.Assert(currentGroup != null, “Unexpected object in code group’s children”);
70
71          if(currentGroup.Name.Equals(“LocalIntranet_Zone”))
72          {
73            // create permission objects that can be used to give the application FullTrust
74            IMembershipCondition membershipCondition = new StrongNameMembershipCondition(appKey, assemblyName, null);
75            PermissionSet permissionSet = new PermissionSet(PermissionState.Unrestricted);
76            PolicyStatement statement = new PolicyStatement(permissionSet);
77
78            // now combine these objects into a CodeGroup
79            CodeGroup appCodeGroup = new UnionCodeGroup(membershipCondition, statement);
80            appCodeGroup.Description = String.Format(“Allow {0} to run off the Intranet with FullTrust”, assemblyName);
81            appCodeGroup.Name = assemblyName;
82
83            // and make this CodeGroup a child of the Intranet zone
84            currentGroup.AddChild(appCodeGroup);
85            addedGroup = true;
86            break;
87          }
88        }
89        break;
90      }
91    }
92
93    // Commit the changes to the policy
95    if(addedGroup)
95      SecurityManager.SavePolicy();
96
97    return addedGroup;
98  }

Comments (2)

  1. Nicole Calinoiu says:

    The CodeGroup.Children property (called at line 63) returns a copy of the child nodes, not a reference to the underlying child node collection. Modifying an item from this copy won’t do much to alter the actual policy hierarchy.

    To fix this, one would need to remove the LocalIntranet_Zone group from its parent group, then add the modified copy back as a new child. The "quick fix" version of this would be to add the following lines between your lines 84 and 85:

    root.RemoveChild(currentGroup);

    root.AddChild(currentGroup);

    There would still be at least two remaining problems:

    1. Neither your original code nor my modification account for the possibility of concurrent modifications to the policy. The only way to be sure that no other process has modified the saved policy between the read and the write would be to place a write lock on the underlying file (security.config in this case) before starting the read. Of course, this would probably block the various SecurityManager operations as well, which means one would need to modify the file’s XML directly.

    2. Members of the local Users group cannot write to the underlying security configuration files, so policy modification is not functionality that should really be built into very many applications via code like this.

  2. Shawn says:

    Yep, you nailed it. Most people don’t realize that the Children property is returning a copy of the child nodes, and modifications to it won’t take hold. Good observations on the other two points as well. In general, I’d recommend code like this only be used for setup operations.

    -Shawn