// // AssemblyRunner.cs // // Author: // Rodrigo Kumpera (rkumpera@novell.com) // // Copyright (C) 2007 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.IO; using System.Net; using System.Diagnostics; using System.Security; using System.Security.Policy; using System.Security.Permissions; using System.Threading; namespace AssemblyRunner { public enum RunResult { valid, unverifiable, invalid, strict, none } public class Runner { private static string program = "\"%COMSPEC%\""; static String ExecuteAndFetchStderr (String command) { ProcessStartInfo psi = new ProcessStartInfo (Environment.ExpandEnvironmentVariables (program), "/c " + command); psi.CreateNoWindow = true; psi.UseShellExecute = false; psi.RedirectStandardError = true; string stderr=null; Process activeProcess=null; Thread stderrThread = new Thread (new ThreadStart (delegate { if (activeProcess != null) stderr = activeProcess.StandardError.ReadToEnd (); })); activeProcess = Process.Start (psi); stderrThread.Start (); activeProcess.WaitForExit(); stderrThread.Join(); activeProcess.Close (); return stderr; } static int ExecuteAndFetchReturnCode (String command) { ProcessStartInfo psi = new ProcessStartInfo (Environment.ExpandEnvironmentVariables (program), "/c " + command); psi.CreateNoWindow = true; psi.UseShellExecute = false; psi.RedirectStandardError = true; Process activeProcess = Process.Start (psi); activeProcess.WaitForExit(); int res = activeProcess.ExitCode; activeProcess.Close (); return res; } static AppDomain NewDomain () { PolicyStatement statement = new PolicyStatement(new PermissionSet(PermissionState.None),PolicyStatementAttribute.Nothing); PermissionSet ps = new PermissionSet(PermissionState.None); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution)); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.Assertion)); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.ControlAppDomain)); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.ControlDomainPolicy)); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.ControlEvidence)); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.ControlPolicy)); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.ControlPrincipal)); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.ControlThread)); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.Infrastructure)); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.RemotingConfiguration)); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.SerializationFormatter)); ps.AddPermission(new FileIOPermission(PermissionState.Unrestricted)); ps.AddPermission(new EnvironmentPermission(PermissionState.Unrestricted)); ps.AddPermission(new ReflectionPermission(PermissionState.Unrestricted)); ps.AddPermission(new RegistryPermission(PermissionState.Unrestricted)); ps.AddPermission(new IsolatedStorageFilePermission(PermissionState.Unrestricted)); ps.AddPermission(new EventLogPermission(PermissionState.Unrestricted)); ps.AddPermission(new PerformanceCounterPermission(PermissionState.Unrestricted)); ps.AddPermission(new DnsPermission(PermissionState.Unrestricted)); ps.AddPermission(new UIPermission(PermissionState.Unrestricted)); PolicyStatement statement1 = new PolicyStatement(ps,PolicyStatementAttribute.Exclusive); CodeGroup group; group = new UnionCodeGroup(new AllMembershipCondition(),statement); group.AddChild(new UnionCodeGroup(new ZoneMembershipCondition(SecurityZone.MyComputer),statement1)); PolicyLevel level = PolicyLevel.CreateAppDomainLevel(); level.RootCodeGroup = group; AppDomain domain = AppDomain.CreateDomain ("test"); domain.SetAppDomainPolicy(level); return domain; } static RunResult testWithAppDomain (String path, String assemblyName) { AppDomain domain = NewDomain (); try { domain.ExecuteAssembly (path); return RunResult.valid; } catch (InvalidProgramException) { return RunResult.invalid; } catch (FileLoadException) { return RunResult.invalid; } catch (VerificationException) { return RunResult.unverifiable; } catch (TypeInitializationException ve) { if (ve.InnerException is VerificationException) return RunResult.unverifiable; Console.WriteLine ("Warning: test {0} thrown exception: {1} ", assemblyName, ve.InnerException); return RunResult.invalid; } catch (MissingMemberException) { return RunResult.invalid; } catch (MemberAccessException) { return RunResult.unverifiable; } catch (TypeLoadException) { return RunResult.unverifiable; } catch (Exception e) { Console.WriteLine ("Warning: test {0} thrown exception {1}", assemblyName, e); return RunResult.valid; } finally { AppDomain.Unload (domain); } } /* * This test with runtime is usefull to assert if the code is unverifiable but not invalid. * This test should be used to diagnose if it's the case of code that was reported as invalid but actually is unverifiable. */ static RunResult testWithRuntime (String path) { String stderr = ExecuteAndFetchStderr (path); bool invalid = stderr.IndexOf ("InvalidProgramException") >= 0 || stderr.IndexOf ("FileLoadException") >= 0; return invalid ? RunResult.invalid : RunResult.valid; } /* * This test can only assert if the code is unverifiable or not. Use it * to check if it's the case for a strict check or the code is verifiable. */ static RunResult testWithPeverify (String path) { if (ExecuteAndFetchReturnCode ("peverify "+path) == 0) return RunResult.valid; return RunResult.unverifiable; } static RunResult decide (RunResult ad, RunResult rt, RunResult pv, String testName) { if (ad == RunResult.valid) { if (rt != RunResult.valid) { Console.WriteLine ("Warning: test {0} returned valid under AD but {1} under runtime. PV said {2}, using runtime choice", testName, rt, pv); return rt; } if (pv != RunResult.valid) return RunResult.strict; return RunResult.valid; } if (ad == RunResult.unverifiable) { //the rt test cannot complain about unverifiable if (pv == RunResult.valid) Console.WriteLine ("Warning: test {0} returned unverifiable under AD but {1} under PV, using AD choice", testName, pv); if (rt == RunResult.invalid) { Console.WriteLine ("Warning: test {0} returned unverifiable under AD but {1} under runtime. PV said {2}, using runtime choice", testName, rt, pv); return rt; } return RunResult.unverifiable; } if (ad == RunResult.invalid) { //in some cases the runtime throws exceptions meant for invalid code but the code is only unverifiable //we double check that by checking if rt returns ok if (pv == RunResult.valid) Console.WriteLine ("Warning: test {0} returned invalid under AD but {1} under PV, using AD choice", testName, pv); if (rt == RunResult.valid) return RunResult.unverifiable; return RunResult.invalid; } Console.WriteLine ("ERROR: test {0} returned an unknown result under ad {1}, runtime said {2} and PV {3} -- FIXME --", testName, ad, rt, pv); return RunResult.none; } static void executeTest (String path, String assemblyName, RunResult expected) { RunResult ad = testWithAppDomain (path, assemblyName); RunResult rt = testWithRuntime (path); RunResult pv = testWithPeverify (path); RunResult veredict = decide (ad, rt, pv, assemblyName); if (veredict != expected) Console.WriteLine ("ERROR: test {0} expected {1} but got {2}", assemblyName, expected, veredict); } /* This method exists because sometimes a VerificationException is throw for invalid code, so we try to run it as standard-alone a check again. */ static void RecheckUnverifiableResult (String path, String assembly, String op) { String stderr = ExecuteAndFetchStderr (path); bool invalid = stderr.IndexOf ("InvalidProgramException") >= 0 || stderr.IndexOf ("FileLoadException") >= 0; if (invalid) { if (!op.Equals ("invalid")) Console.WriteLine ("Test returned invalid: "+assembly); }else if (!op.Equals ("unverifiable")) Console.WriteLine ("Test returned unverifiable: "+assembly); } public static void Main (String[] args) { String dirName = "."; if (args.Length > 0) dirName = args[0]; DirectoryInfo dir = new DirectoryInfo (dirName); foreach (FileInfo file in dir.GetFiles ()) { try { RunResult rr = RunResult.none; if (file.Name.StartsWith ("strict_")) rr = RunResult.strict; else if (file.Name.StartsWith ("valid_")) rr = RunResult.valid; else if (file.Name.StartsWith ("unverifiable_")) rr = RunResult.unverifiable; else if (file.Name.StartsWith ("invalid_")) rr = RunResult.invalid; if (file.Name.EndsWith (".exe") && rr != RunResult.none) executeTest (file.FullName, file.Name, rr); } catch (Exception e) { Console.WriteLine ("Warning: test {0} thrown exception {1}", file.FullName, e); } } } } }