Fully Trust a share in .Net 2.0, 3.0 and 3.5

Just some quick info for those struggling with the same problems.

When i google for this almost all answers predate the .net 3.0 framework. Everybody knows that 3.0 are a set of libraries that extend 2.0. what i missed was that 3.5 uses the same core assemblies as 2.0 including the same security settings. This means you still use the CasPol installed from the 2.0 to set full trust in 3.5. Be aware thought that you’ll need to run it for the 32-bit version and 64-bit version separately.

There is a nice post on how to do this from the .net security blog from which I included the next fragment:

since network shares by default only get localintranet permissions, it's relatively common to want to use CasPol to fully trust some shares that you control and know are safe.  However, CasPol syntax being what it is, the command to do this isn't immediately obvious.  if i wanted to trust everything on the share \\shawnfa-srv\tools, the command:

caspol.exe -m -ag 1.2 -url file://\\ShawnFa-Srv/Tools/* FullTrust

would setup the policy to do what i needed.  lets break down this command:

  • -m  - modify the machine level of the policy.  this is needed, since the machine level is where all of the default policy lives.  on nt platforms it's also the default level that CasPol works with, however on Win9x, CasPol will default to the user level, so putting -m in the command line explicitly tells CasPol to use the correct level.
  • -ag 1.2  - add a code group under group 1.2.  in the default policy, group 1.2 is the localintranet group, so the new code group that we're creating will only be checked if the file comes from the intranet.
  • -url file://\\ShawnFa-Srv/Tools/- The membership condition for the new code group should be a UrlMembershipCondition, and it should match anything with a URL that starts with file://ShawnFa-Srv/Tools, meaning that any file on the \\ShawnFa-Srv\Tools share will match this code group.
  • fulltrust  - the permission set to grant assemblies that match the code group.  in this case, fulltrust.

Executing the above command will give you a yes/no confirmation prompt which you can suppress by adding the –pp off switch. Now it’s perfectly usable in scripts.

I kept wondering about 2 things:

  • what if the mapped folder was included in the trusted group?
  • where does it store these settings?

Get the code groups.

To get the right -ag argument for the trusted group call:

caspol.exe –lg

which will give you something like this:

Microsoft (R) .NET Framework CasPol 2.0.50727.3053
Copyright (c) Microsoft Corporation.  All rights reserved.

Security is ON
Execution checking is ON
Policy change prompt is ON

Level = Machine

Code Groups:

1.  All code: Nothing
   1.1.  Zone - MyComputer: FullTrust
      1.1.1.  StrongName - 002400000...: FullTrust
      1.1.2.  StrongName - 000000000...: FullTrust
   1.2.  Zone - Intranet: LocalIntranet
      1.2.1.  All code: Same site Web
      1.2.2.  All code: Same directory FileIO - 'Read, PathDiscovery'
   1.3.  Zone - Internet: Internet
      1.3.1.  All code: Same site Web
   1.4.  Zone - Untrusted: Nothing
   1.5.  Zone - Trusted: Internet
      1.5.1.  All code: Same site Web
Success

From this output we now know that we need to use 1.5 as an –ag argument to FullTrust a path from the Trusted group.

Where is it stored?

The security information is stored in the security.config file (duh) in the following location:

%WINDIR%\Microsoft.NET\Framework\v2.0.50727\CONFIG

Our example inserts the following xml under the Trusted codegroup element:

<CodeGroup class="UnionCodeGroup" version="1" PermissionSetName="FullTrust">
    <IMembershipCondition class="UrlMembershipCondition" version="1" Url="file://\\ShawnFa-Srv/Tools/*"/>
</CodeGroup>

If you don’t want the settings removed by a call to:

CasPol -all –reset

You can copy the security.config to security.config.default

Tags: , , , , , , , , ,

Detect if an assembly is a managed assembly

While using a great IOC/DI container (read for those not familiar with it read this post) I wrote some code to auto detect and auto load the necessary dependencies (I really, really, REALLY hate the XML configuration file so …) Besides a lot of other issues with this method worth a blog post on it’s own, the whole thing crashed as soon as an unmanaged assembly was found in the path.

After some googling I found a few ways to solve my problem:

Use .Net Reflection:

Try to do a AssemblyName.GetAssemblyName(path); If it throws an exception it’s not a managed assembly. As you would suspect this is horribly slow.

Read the binary data from the assembly to detect the assembly type.

Actually the .Net managed assemblies are with specific values in the header. To read these we could use the command and then walk through the headers with pointers as explained on this site. Since this loads the assembly in memory it was a no-go for me.

There are alot of unmanaged solutions out there (read here and here) that are candidates to port to c# but Rupreet’s weblog saved the day. And here are the relevant code fragments of my AssemblyInfo class:

   1:  try
   2:  {
   3:      if (dataDictionaryRVA == null)
   4:      {
   5:          GetHeaders();
   6:      }
   7:      return dataDictionaryRVA[14] != 0;
   8:  }
   9:  catch (Exception)
  10:  {
  11:      // when an error occurs return false.
  12:  }
  13:  return false;

Obviously the interesting code is inside the GetHeaders method including the comments from Rupreet:

   1: dataDictionaryRVA = new uint[16];
   2: dataDictionarySize = new uint[16];
   3:  
   4: using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
   5: {
   6:     BinaryReader reader = new BinaryReader(fs);
   7:  
   8:     //PE Header starts @ 0x3C (60). Its a 4 byte header.
   9:     fs.Position = PeHeaderStart;
  10:     peHeader = reader.ReadUInt32();
  11:  
  12:     //Moving to PE Header start location...
  13:     fs.Position = peHeader;
  14:     peHeaderSignature = reader.ReadUInt32();
  15:  
  16:     //We can also show all these value, but we will be       
  17:     //limiting to the CLI header test.
  18:     machine = reader.ReadUInt16();
  19:     sections = reader.ReadUInt16();
  20:     timestamp = reader.ReadUInt32();
  21:     pSymbolTable = reader.ReadUInt32();
  22:     noOfSymbol = reader.ReadUInt32();
  23:     optionalHeaderSize = reader.ReadUInt16();
  24:     characteristics = reader.ReadUInt16();
  25:  
  26:     /*
  27:     Now we are at the end of the PE Header and from here, the
  28:     PE Optional Headers starts...
  29:     To go directly to the datadictionary, we'll increase the      
  30:     stream’s current position to with 96 (0x60). 96 because,
  31:     28 for Standard fields
  32:     68 for NT-specific fields
  33:     From here DataDictionary starts...and its of total 128 bytes. DataDictionay has 16 directories in total,
  34:     doing simple maths 128/16 = 8.
  35:     So each directory is of 8 bytes.
  36:     In this 8 bytes, 4 bytes is of RVA and 4 bytes of Size.
  37: 
  38:     btw, the 15th directory consist of CLR header! if its 0, its not a CLR file :)
  39:     */
  40:  
  41:     dataDictionaryStart = Convert.ToUInt16(Convert.ToUInt16(fs.Position) + 0x60);
  42:     fs.Position = dataDictionaryStart;
  43:  
  44:     for (int i = 0; i < 15; i++)
  45:     {
  46:         dataDictionaryRVA[i] = reader.ReadUInt32();
  47:         dataDictionarySize[i] = reader.ReadUInt32();
  48:     }
  49:  
  50:     fs.Close();
  51: }

My complete class implementation is here:

   1: using System;
   2: using System.IO;
   3: using System.Reflection;
   4:  
   5: namespace Williame.Koen.Blog
   6: {
   7:     /// <summary>
   8:     /// Returns information about an assembly without loading it into the appdomain.
   9:     /// </summary>
  10:     public class AssemblyInfo
  11:     {
  12:         /// <summary>
  13:         /// PE Header starts @ 0x3C (60). Its a 4 byte header.
  14:         /// </summary>
  15:         public const long PeHeaderStart = 0x3C;
  16:  
  17:         protected uint peHeader;
  18:         protected uint peHeaderSignature;
  19:         protected ushort machine;
  20:         protected ushort sections;
  21:         protected uint timestamp;
  22:         protected uint pSymbolTable;
  23:         protected uint noOfSymbol;
  24:         protected ushort optionalHeaderSize;
  25:         protected ushort characteristics;
  26:         protected ushort dataDictionaryStart;
  27:         protected uint[] dataDictionaryRVA;
  28:         protected uint[] dataDictionarySize;
  29:         protected string file;
  30:         protected AssemblyName name; 
  31:  
  32:         public AssemblyInfo(string fileName)
  33:         {
  34:             this.file = Path.GetFullPath(fileName);
  35:         }
  36:  
  37:         protected void GetHeaders()
  38:         {
  39:             dataDictionaryRVA = new uint[16];
  40:             dataDictionarySize = new uint[16];
  41:  
  42:             using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
  43:             {
  44:                 BinaryReader reader = new BinaryReader(fs);
  45:  
  46:                 //PE Header starts @ 0x3C (60). Its a 4 byte header.
  47:                 fs.Position = PeHeaderStart;
  48:                 peHeader = reader.ReadUInt32();
  49:  
  50:                 //Moving to PE Header start location...
  51:                 fs.Position = peHeader;
  52:                 peHeaderSignature = reader.ReadUInt32();
  53:  
  54:                 //We can also show all these value, but we will be       
  55:                 //limiting to the CLI header test.
  56:                 machine = reader.ReadUInt16();
  57:                 sections = reader.ReadUInt16();
  58:                 timestamp = reader.ReadUInt32();
  59:                 pSymbolTable = reader.ReadUInt32();
  60:                 noOfSymbol = reader.ReadUInt32();
  61:                 optionalHeaderSize = reader.ReadUInt16();
  62:                 characteristics = reader.ReadUInt16();
  63:  
  64:                 /*
  65:                 Now we are at the end of the PE Header and from here, the
  66:                 PE Optional Headers starts...
  67:                 To go directly to the datadictionary, we'll increase the      
  68:                 stream’s current position to with 96 (0x60). 96 because,
  69:                 28 for Standard fields
  70:                 68 for NT-specific fields
  71:                 From here DataDictionary starts...and its of total 128 bytes. DataDictionay has 16 directories in total,
  72:                 doing simple maths 128/16 = 8.
  73:                 So each directory is of 8 bytes.
  74:                 In this 8 bytes, 4 bytes is of RVA and 4 bytes of Size.
  75: 
  76:                 btw, the 15th directory consist of CLR header! if its 0, its not a CLR file :)
  77:                 */
  78:  
  79:                 dataDictionaryStart = Convert.ToUInt16(Convert.ToUInt16(fs.Position) + 0x60);
  80:                 fs.Position = dataDictionaryStart;
  81:  
  82:                 for (int i = 0; i < 15; i++)
  83:                 {
  84:                     dataDictionaryRVA[i] = reader.ReadUInt32();
  85:                     dataDictionarySize[i] = reader.ReadUInt32();
  86:                 }
  87:  
  88:                 fs.Close();
  89:             }
  90:         }
  91:  
  92:         public bool IsManaged
  93:         {
  94:             get
  95:             {
  96:                 try
  97:                 {
  98:                     if (dataDictionaryRVA == null)
  99:                     {
 100:                         GetHeaders();
 101:                     }
 102:                     return dataDictionaryRVA[14] != 0;
 103:                 }
 104:                 catch (Exception)
 105:                 {
 106:                     // when an error occurs return false.
 107:                 }
 108:                 return false;
 109:             }
 110:         }
 111:  
 112:         public AssemblyName Name
 113:         {
 114:             get
 115:             {
 116:                 if (name == null)
 117:                 {
 118:                     name = AssemblyName.GetAssemblyName(file);
 119:                 }
 120:                 return name;
 121:             }
 122:         }
 123:     }
 124: }

As you can see, I also included the AssemblyName which can give me some more information withtout loading the assembly in memory.

Issues:

  • This is example code so be careful with it in production code.
  • In addition, check the "magic" field of the PE header (the first 2 bytes) to ensure it is a 32-bit image file (0x010B). The data directory table for 64-bit PE headers start at offset 112 - as opposed to 96 for 32-bit headers.
  • This example ignores recommendations in Microsoft's PE specification to check the values of size fields so you don't read the wrong data

Tags: , , , , , ,

My first blog post :)

I finally did it… I started my blog. (small victory dance by yours sincerely).

I am currently trying out Windows Live Writer and BlogEngine.Net and am loving them.

Now after the obligatory blabbering in this typical first post. I promise to blog about some more interesting things. Programming, programming, and of course software development :).

Tags: , ,