9489876ae97ecf057ef636e6b7072f3d174ef0a0
[mono.git] / mcs / tools / tuner / Mono.Tuner / InjectSecurityAttributes.cs
1 //
2 // InjectSecurityAttributes.cs
3 //
4 // Author:
5 //   Jb Evain (jbevain@novell.com)
6 //
7 // (C) 2009 Novell, Inc.
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.Collections;
31 using System.IO;
32 using System.Linq;
33 using System.Text;
34
35 using Mono.Linker;
36 using Mono.Linker.Steps;
37
38 using Mono.Cecil;
39 using Mono.Cecil.Cil;
40
41 namespace Mono.Tuner {
42
43         public class InjectSecurityAttributes : BaseStep {
44
45                 enum TargetKind {
46                         Type,
47                         Method,
48                 }
49
50                 protected enum AttributeType {
51                         Critical,
52                         SafeCritical,
53                 }
54
55                 const string _safe_critical = "System.Security.SecuritySafeCriticalAttribute";
56                 const string _critical = "System.Security.SecurityCriticalAttribute";
57                 const string _system_void = "System.Void";
58
59                 const string sec_attr_folder = "secattrs";
60
61                 protected AssemblyDefinition _assembly;
62
63                 MethodDefinition _safe_critical_ctor;
64                 MethodDefinition _critical_ctor;
65                 TypeDefinition _void_type;
66
67                 string data_folder;
68
69                 protected override bool ConditionToProcess ()
70                 {
71                         if (!Context.HasParameter (sec_attr_folder)) {
72                                 Console.Error.WriteLine ("Warning: no secattrs folder specified.");
73                                 return false;
74                         }
75
76                         data_folder = Context.GetParameter (sec_attr_folder);
77                         return true;
78                 }
79
80                 protected override void ProcessAssembly (AssemblyDefinition assembly)
81                 {
82                         if (Annotations.GetAction (assembly) != AssemblyAction.Link)
83                                 return;
84
85                         string secattr_file = Path.Combine (
86                                 data_folder,
87                                 assembly.Name.Name + ".secattr");
88
89                         if (!File.Exists (secattr_file)) {
90                                 Console.Error.WriteLine ("Warning: file '{0}' not found, skipping.", secattr_file);
91                                 return;
92                         }
93
94                         _assembly = assembly;
95
96                         // remove existing [SecurityCritical] and [SecuritySafeCritical]
97                         RemoveSecurityAttributes ();
98
99                         // add [SecurityCritical] and [SecuritySafeCritical] from the data file
100                         ProcessSecurityAttributeFile (secattr_file);
101                 }
102
103                 protected void RemoveSecurityAttributes ()
104                 {
105                         foreach (TypeDefinition type in _assembly.MainModule.Types) {
106                                 if (RemoveSecurityAttributes (type))
107                                         type.HasSecurity = false;
108
109                                 if (type.HasMethods) {
110                                         foreach (MethodDefinition method in type.Methods) {
111                                                 if (RemoveSecurityAttributes (method))
112                                                         method.HasSecurity = false;
113                                         }
114                                 }
115                         }
116                 }
117
118                 static bool RemoveSecurityDeclarations (ISecurityDeclarationProvider provider)
119                 {
120                         // also remove already existing CAS security declarations
121
122                         if (provider == null)
123                                 return false;
124
125                         if (!provider.HasSecurityDeclarations)
126                                 return false;
127
128                         provider.SecurityDeclarations.Clear ();
129                         return true;
130                 }
131
132                 static bool RemoveSecurityAttributes (ICustomAttributeProvider provider)
133                 {
134                         bool result = RemoveSecurityDeclarations (provider as ISecurityDeclarationProvider);
135
136                         if (!provider.HasCustomAttributes)
137                                 return result;
138
139                         var attributes = provider.CustomAttributes;
140                         for (int i = 0; i < attributes.Count; i++) {
141                                 CustomAttribute attribute = attributes [i];
142                                 switch (attribute.Constructor.DeclaringType.FullName) {
143                                 case _safe_critical:
144                                 case _critical:
145                                         attributes.RemoveAt (i--);
146                                         break;
147                                 }
148                         }
149                         return result;
150                 }
151
152                 void ProcessSecurityAttributeFile (string file)
153                 {
154                         using (StreamReader reader = File.OpenText (file)) {
155                                 string line;
156                                 while ((line = reader.ReadLine ()) != null)
157                                         ProcessLine (line);
158                         }
159                 }
160
161                 void ProcessLine (string line)
162                 {
163                         if (line == null || line.Length < 6 || line [0] == '#')
164                                 return;
165
166                         int sep = line.IndexOf (": ");
167                         if (sep == -1)
168                                 return;
169
170                         string marker = line.Substring (0, sep);
171                         string target = line.Substring (sep + 2);
172
173                         ProcessSecurityAttributeEntry (
174                                 DecomposeAttributeType (marker),
175                                 DecomposeTargetKind (marker),
176                                 target);
177                 }
178
179                 static AttributeType DecomposeAttributeType (string marker)
180                 {
181                         if (marker.StartsWith ("SC"))
182                                 return AttributeType.Critical;
183                         else if (marker.StartsWith ("SSC"))
184                                 return AttributeType.SafeCritical;
185                         else
186                                 throw new ArgumentException ();
187                 }
188
189                 static TargetKind DecomposeTargetKind (string marker)
190                 {
191                         switch (marker [marker.Length - 1]) {
192                         case 'T':
193                                 return TargetKind.Type;
194                         case 'M':
195                                 return TargetKind.Method;
196                         default:
197                                 throw new ArgumentException ();
198                         }
199                 }
200
201                 public static bool NeedsDefaultConstructor (TypeDefinition type)
202                 {
203                         if (type.IsInterface)
204                                 return false;
205
206                         TypeReference base_type = type.BaseType;
207                         if ((base_type == null) || (base_type.Namespace != "System"))
208                                 return true;
209
210                         return ((base_type.Name != "Delegate") && (base_type.Name != "MulticastDelegate"));
211                 }
212
213                 void ProcessSecurityAttributeEntry (AttributeType type, TargetKind kind, string target)
214                 {
215                         ICustomAttributeProvider provider = GetTarget (kind, target);
216                         if (provider == null) {
217                                 Console.Error.WriteLine ("Warning: entry '{0}' could not be found", target);
218                                 return;
219                         }
220
221                         // we need to be smarter when applying the attributes (mostly SC) to types
222                         if (kind == TargetKind.Type) {
223                                 TypeDefinition td = (provider as TypeDefinition);
224                                 // ensure [SecurityCritical] types (well most) have a default constructor
225                                 if ((type == AttributeType.Critical) && NeedsDefaultConstructor (td)) {
226                                         if (GetDefaultConstructor (td) == null) {
227                                                 // Console.Error.WriteLine ("Info: adding default ctor for '{0}'", td);
228                                                 td.Methods.Add (CreateDefaultConstructor ());
229                                         }
230                                 }
231
232                                 // it's easier for some tools (e.g. less false positives in fxcop) 
233                                 // and also quicker for the runtime (one less lookup) if all methods gets decorated
234                                 foreach (MethodDefinition method in td.Methods) {
235                                         bool skip = false;
236
237                                         AttributeType mtype = type;
238                                         // there are cases where an SC cannot be applied to some methods
239                                         switch (method.Name) {
240                                         // e.g. everything we override from System.Object (which is transparent)
241                                         case "Equals":
242                                                 skip = method.Parameters.Count == 1 && method.Parameters [0].ParameterType.FullName == "System.Object";
243                                                 break;
244                                         case "Finalize":
245                                         case "GetHashCode":
246                                         case "ToString":
247                                                 skip = !method.HasParameters;
248                                                 break;
249                                         // e.g. some transparent interfaces, like IDisposable (implicit or explicit)
250                                         // downgrade some SC into SSC to respect the override/inheritance rules
251                                         case "System.IDisposable.Dispose":
252                                         case "Dispose":
253                                                 skip = !method.HasParameters;
254                                                 break;
255                                         }
256
257                                         if (skip)
258                                                 continue;
259
260                                         switch (mtype) {
261                                         case AttributeType.Critical:
262                                                 AddCriticalAttribute (method);
263                                                 break;
264                                         case AttributeType.SafeCritical:
265                                                 AddSafeCriticalAttribute (method);
266                                                 break;
267                                         }
268                                 }
269                         }
270
271                         switch (type) {
272                         case AttributeType.Critical:
273                                 AddCriticalAttribute (provider);
274                                 break;
275                         case AttributeType.SafeCritical:
276                                 AddSafeCriticalAttribute (provider);
277                                 break;
278                         }
279                 }
280
281                 protected void AddCriticalAttribute (ICustomAttributeProvider provider)
282                 {
283                         // a [SecurityCritical] replaces a [SecuritySafeCritical]
284                         if (HasSecurityAttribute (provider, AttributeType.SafeCritical))
285                                 RemoveSecurityAttributes (provider);
286
287                         AddSecurityAttribute (provider, AttributeType.Critical);
288                 }
289
290                 void AddSafeCriticalAttribute (ICustomAttributeProvider provider)
291                 {
292                         // a [SecuritySafeCritical] is ignored if a [SecurityCritical] is present
293                         if (HasSecurityAttribute (provider, AttributeType.Critical))
294                                 return;
295
296                         AddSecurityAttribute (provider, AttributeType.SafeCritical);
297                 }
298
299                 void AddSecurityAttribute (ICustomAttributeProvider provider, AttributeType type)
300                 {
301                         if (HasSecurityAttribute (provider, type))
302                                 return;
303
304                         var attributes = provider.CustomAttributes;
305                         switch (type) {
306                         case AttributeType.Critical:
307                                 attributes.Add (CreateCriticalAttribute ());
308                                 break;
309                         case AttributeType.SafeCritical:
310                                 attributes.Add (CreateSafeCriticalAttribute ());
311                                 break;
312                         }
313                 }
314
315                 protected static bool HasSecurityAttribute (ICustomAttributeProvider provider, AttributeType type)
316                 {
317                         if (!provider.HasCustomAttributes)
318                                 return false;
319
320                         foreach (CustomAttribute attribute in provider.CustomAttributes) {
321                                 switch (attribute.Constructor.DeclaringType.Name) {
322                                 case _critical:
323                                         if (type == AttributeType.Critical)
324                                                 return true;
325
326                                         break;
327                                 case _safe_critical:
328                                         if (type == AttributeType.SafeCritical)
329                                                 return true;
330
331                                         break;
332                                 }
333                         }
334
335                         return false;
336                 }
337
338                 ICustomAttributeProvider GetTarget (TargetKind kind, string target)
339                 {
340                         switch (kind) {
341                         case TargetKind.Type:
342                                 return GetType (target);
343                         case TargetKind.Method:
344                                 return GetMethod (target);
345                         default:
346                                 throw new ArgumentException ();
347                         }
348                 }
349
350                 TypeDefinition GetType (string fullname)
351                 {
352                         return _assembly.MainModule.GetType (fullname);
353                 }
354
355                 MethodDefinition GetMethod (string signature)
356                 {
357                         int pos = signature.IndexOf (" ");
358                         if (pos == -1)
359                                 throw new ArgumentException ();
360
361                         string tmp = signature.Substring (pos + 1);
362
363                         pos = tmp.IndexOf ("::");
364                         if (pos == -1)
365                                 throw new ArgumentException ();
366
367                         string type_name = tmp.Substring (0, pos);
368
369                         int parpos = tmp.IndexOf ("(");
370                         if (parpos == -1)
371                                 throw new ArgumentException ();
372
373                         string method_name = tmp.Substring (pos + 2, parpos - pos - 2);
374
375                         TypeDefinition type = GetType (type_name);
376                         if (type == null)
377                                 return null;
378
379                         return GetMethod (type.Methods, signature);
380                 }
381
382                 static MethodDefinition GetMethod (IEnumerable methods, string signature)
383                 {
384                         foreach (MethodDefinition method in methods)
385                                 if (GetFullName (method) == signature)
386                                         return method;
387
388                         return null;
389                 }
390
391                 static string GetFullName (MethodReference method)
392                 {
393                         var sentinel = method.Parameters.FirstOrDefault (p => p.ParameterType.IsSentinel);
394                         var sentinel_pos = -1;
395                         if (sentinel != null)
396                                 sentinel_pos = method.Parameters.IndexOf (sentinel);
397
398                         StringBuilder sb = new StringBuilder ();
399                         sb.Append (method.ReturnType.FullName);
400                         sb.Append (" ");
401                         sb.Append (method.DeclaringType.FullName);
402                         sb.Append ("::");
403                         sb.Append (method.Name);
404                         if (method.HasGenericParameters) {
405                                 sb.Append ("<");
406                                 for (int i = 0; i < method.GenericParameters.Count; i++ ) {
407                                         if (i > 0)
408                                                 sb.Append (",");
409                                         sb.Append (method.GenericParameters [i].Name);
410                                 }
411                                 sb.Append (">");
412                         }
413                         sb.Append ("(");
414                         if (method.HasParameters) {
415                                 for (int i = 0; i < method.Parameters.Count; i++) {
416                                         if (i > 0)
417                                                 sb.Append (",");
418
419                                         if (i == sentinel_pos)
420                                                 sb.Append ("...,");
421
422                                         sb.Append (method.Parameters [i].ParameterType.FullName);
423                                 }
424                         }
425                         sb.Append (")");
426                         return sb.ToString ();
427                 }
428
429                 static MethodDefinition GetDefaultConstructor (TypeDefinition type)
430                 {
431                         foreach (MethodDefinition ctor in type.Methods.Where (m => m.IsConstructor))
432                                 if (!ctor.IsStatic && !ctor.HasParameters)
433                                         return ctor;
434
435                         return null;
436                 }
437
438                 MethodDefinition GetSafeCriticalCtor ()
439                 {
440                         if (_safe_critical_ctor != null)
441                                 return _safe_critical_ctor;
442
443                         TypeDefinition safe_critical_type = Context.GetType (_safe_critical);
444                         if (safe_critical_type == null)
445                                 throw new InvalidOperationException (String.Format ("{0} type not found", _safe_critical));
446
447                         _safe_critical_ctor = GetDefaultConstructor (safe_critical_type);
448                         return _safe_critical_ctor;
449                 }
450
451                 MethodDefinition GetCriticalCtor ()
452                 {
453                         if (_critical_ctor != null)
454                                 return _critical_ctor;
455
456                         TypeDefinition critical_type = Context.GetType (_critical);
457                         if (critical_type == null)
458                                 throw new InvalidOperationException (String.Format ("{0} type not found", _critical));
459
460                         _critical_ctor = GetDefaultConstructor (critical_type);
461                         return _critical_ctor;
462                 }
463
464                 TypeDefinition GetSystemVoid ()
465                 {
466                         if (_void_type != null)
467                                 return _void_type;
468
469                         _void_type = Context.GetType (_system_void);
470                         if (_void_type == null)
471                                 throw new InvalidOperationException (String.Format ("{0} type not found", _system_void));
472
473                         return _void_type;
474                 }
475
476                 MethodReference Import (MethodDefinition method)
477                 {
478                         return _assembly.MainModule.Import (method);
479                 }
480
481                 CustomAttribute CreateSafeCriticalAttribute ()
482                 {
483                         return new CustomAttribute (Import (GetSafeCriticalCtor ()));
484                 }
485
486                 CustomAttribute CreateCriticalAttribute ()
487                 {
488                         return new CustomAttribute (Import (GetCriticalCtor ()));
489                 }
490
491                 MethodDefinition CreateDefaultConstructor ()
492                 {
493                         MethodDefinition method = new MethodDefinition (".ctor", 
494                                 MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, 
495                                 GetSystemVoid ());
496                         method.Body.Instructions.Add (Instruction.Create (OpCodes.Ret));
497                         return method;
498                 }
499         }
500 }