OLD | NEW |
| (Empty) |
1 /// Copyright (c) Microsoft Corporation. All rights reserved. | |
2 | |
3 using System; | |
4 using System.Collections; | |
5 using System.Collections.Generic; | |
6 using System.Diagnostics; | |
7 using System.Globalization; | |
8 using System.IO; | |
9 using System.Runtime.InteropServices; | |
10 using System.Security.Permissions; | |
11 using System.Text; | |
12 using Microsoft.VisualStudio; | |
13 using Microsoft.VisualStudio.Build.ComInteropWrapper; | |
14 using Microsoft.VisualStudio.Shell; | |
15 using Microsoft.VisualStudio.Shell.Interop; | |
16 using Microsoft.Win32; | |
17 | |
18 namespace Microsoft.VisualStudio.Project | |
19 { | |
20 /// <summary> | |
21 /// Does security validation of a project before loading the project | |
22 /// </summary> | |
23 public class ProjectSecurityChecker : IDisposable | |
24 { | |
25 #region constants | |
26 /// <summary> | |
27 /// The dangereous target property. | |
28 /// </summary> | |
29 internal const string DangerousTargetProperty = "LoadTimeSensiti
veTargets"; | |
30 | |
31 /// <summary> | |
32 /// The dangereous properties property. | |
33 /// </summary> | |
34 internal const string DangerousPropertyProperty = "LoadTimeSensi
tiveProperties"; | |
35 | |
36 /// <summary> | |
37 /// The dangereous items property. | |
38 /// </summary> | |
39 internal const string DangerousItemsProperty = "LoadTimeSensitiv
eItems"; | |
40 | |
41 /// <summary> | |
42 /// The check item locations property. | |
43 /// </summary> | |
44 internal const string CheckItemLocationProperty = "LoadTimeCheck
ItemLocation"; | |
45 | |
46 /// <summary> | |
47 /// The dangereous list item separator. | |
48 /// </summary> | |
49 internal const string DangerousListSeparator = ";"; | |
50 | |
51 /// <summary> | |
52 /// The project directory property. | |
53 /// </summary> | |
54 internal const string ProjectDirectoryProperty = "MSBuildProject
Directory"; | |
55 | |
56 /// <summary> | |
57 /// The default dangereous properties. | |
58 /// </summary> | |
59 internal const string DefaultDangerousProperties = "LoadTimeSens
itiveTargets;LoadTimeSensitiveProperties;LoadTimeSensitiveItems;LoadTimeCheckIte
mLocation;"; | |
60 | |
61 /// <summary> | |
62 /// The default dangereous targets. | |
63 /// </summary> | |
64 internal const string DefaultDangerousTargets = "Compile;GetFram
eworkPaths;AllProjectOutputGroups;AllProjectOutputGroupsDependencies;CopyRunEnvi
ronmentFiles;ResolveComReferences;ResolveAssemblyReferences;ResolveNativeReferen
ces;"; | |
65 | |
66 /// <summary> | |
67 /// The default dangereous items. | |
68 /// </summary> | |
69 internal const string DefaultDangerousItems = ";"; | |
70 | |
71 /// <summary> | |
72 /// Defined the safe imports subkey in the registry. | |
73 /// </summary> | |
74 internal const string SafeImportsSubkey = @"MSBuild\SafeImports"
; | |
75 #endregion | |
76 | |
77 #region fields | |
78 /// <summary> | |
79 /// Defines an object that will be a mutex for this object for s
ynchronizing thread calls. | |
80 /// </summary> | |
81 private static volatile object Mutex = new object(); | |
82 | |
83 /// <summary> | |
84 /// Flag determining if the object has been disposed. | |
85 /// </summary> | |
86 private bool isDisposed; | |
87 | |
88 /// <summary> | |
89 /// The associated project shim for the project file | |
90 /// </summary> | |
91 private ProjectShim projectShim; | |
92 | |
93 /// <summary> | |
94 /// The security check helper object used to call out to do nece
ssary security checkings. | |
95 /// </summary> | |
96 private SecurityCheckHelper securityCheckHelper = new SecurityCh
eckHelper(); | |
97 | |
98 /// <summary> | |
99 /// The associated service provider. | |
100 /// </summary> | |
101 private IServiceProvider serviceProvider; | |
102 | |
103 #endregion | |
104 | |
105 #region properties | |
106 /// <summary> | |
107 /// The associated project shim for the project file | |
108 /// </summary> | |
109 /// <devremark>The project shim is made internal in order to be
able to be passed to the user project.</devremark> | |
110 internal protected ProjectShim ProjectShim | |
111 { | |
112 get { return this.projectShim; } | |
113 } | |
114 | |
115 /// <summary> | |
116 /// The security check helper that will be used to perform the n
ecessary checkings. | |
117 /// </summary> | |
118 protected SecurityCheckHelper SecurityCheckHelper | |
119 { | |
120 get { return this.securityCheckHelper; } | |
121 } | |
122 | |
123 /// <summary> | |
124 /// The associated service provider. | |
125 /// </summary> | |
126 protected IServiceProvider ServiceProvider | |
127 { | |
128 get | |
129 { | |
130 return this.serviceProvider; | |
131 } | |
132 } | |
133 #endregion | |
134 | |
135 #region ctors | |
136 /// <summary> | |
137 /// Overloaded Constructor | |
138 /// </summary> | |
139 /// <param name="projectFilePath">path to the project file</para
m> | |
140 /// <param name="serviceProvider">A service provider.</param> | |
141 public ProjectSecurityChecker(IServiceProvider serviceProvider,
string projectFilePath) | |
142 { | |
143 if(serviceProvider == null) | |
144 { | |
145 throw new ArgumentNullException("serviceProvider
"); | |
146 } | |
147 | |
148 if(String.IsNullOrEmpty(projectFilePath)) | |
149 { | |
150 throw new ArgumentException(SR.GetString(SR.Para
meterCannotBeNullOrEmpty, CultureInfo.CurrentUICulture), "projectFilePath"); | |
151 } | |
152 | |
153 this.serviceProvider = serviceProvider; | |
154 | |
155 // Instantiate a new project shim that we are going to u
se for security checkings. | |
156 EngineShim engine = new EngineShim(); | |
157 this.projectShim = engine.CreateNewProject(); | |
158 this.projectShim.Load(projectFilePath); | |
159 } | |
160 #endregion | |
161 | |
162 #region IDisposable Members | |
163 | |
164 /// <summary> | |
165 /// The IDispose interface Dispose method for disposing the obje
ct determinastically. | |
166 /// </summary> | |
167 public void Dispose() | |
168 { | |
169 this.Dispose(true); | |
170 GC.SuppressFinalize(this); | |
171 } | |
172 | |
173 #endregion | |
174 | |
175 #region virtual methods | |
176 /// <summary> | |
177 /// Check if the project is safe at load/design time | |
178 /// </summary> | |
179 /// <param name="securityErrorMessage">If the project is not saf
e contains an error message, describing the reason.</param> | |
180 /// <returns>true if the project is safe, false otherwise</retur
ns> | |
181 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Desi
gn", "CA1021:AvoidOutParameters", MessageId = "0#", | |
182 Justification = "The error message needs to be an out pa
rameter. We are following here the Try... method patterns.")] | |
183 public virtual bool IsProjectSafeAtLoadTime(out string securityE
rrorMessage) | |
184 { | |
185 securityErrorMessage = String.Empty; | |
186 | |
187 StringBuilder securityMessageMaker = new StringBuilder()
; | |
188 int counter = 0; | |
189 string tempMessage; | |
190 | |
191 // STEP 1: Check direct imports. | |
192 if(!this.IsProjectSafeWithImports(out tempMessage)) | |
193 { | |
194 ProjectSecurityChecker.FormatMessage(securityMes
sageMaker, ++counter, tempMessage); | |
195 securityErrorMessage = tempMessage; | |
196 } | |
197 | |
198 // STEP 2: Check dangerous properties | |
199 if(!this.IsProjectSafeWithProperties(out tempMessage)) | |
200 { | |
201 ProjectSecurityChecker.FormatMessage(securityMes
sageMaker, ++counter, tempMessage); | |
202 securityErrorMessage = tempMessage; | |
203 } | |
204 | |
205 // STEP 3: Check dangerous targets | |
206 if(!this.IsProjectSafeWithTargets(out tempMessage)) | |
207 { | |
208 ProjectSecurityChecker.FormatMessage(securityMes
sageMaker, ++counter, tempMessage); | |
209 securityErrorMessage = tempMessage; | |
210 } | |
211 | |
212 // STEP 4: Check dangerous items | |
213 if(!this.IsProjectSafeWithItems(out tempMessage)) | |
214 { | |
215 ProjectSecurityChecker.FormatMessage(securityMes
sageMaker, ++counter, tempMessage); | |
216 securityErrorMessage = tempMessage; | |
217 } | |
218 | |
219 // STEP 5: Check UsingTask tasks | |
220 if(!this.IsProjectSafeWithUsingTasks(out tempMessage)) | |
221 { | |
222 ProjectSecurityChecker.FormatMessage(securityMes
sageMaker, ++counter, tempMessage); | |
223 securityErrorMessage = tempMessage; | |
224 } | |
225 | |
226 // STEP 6: Check for items defined within the LoadTimeCh
eckItemLocation, whether they are defined in safe locations | |
227 if(!this.CheckItemsLocation(out tempMessage)) | |
228 { | |
229 securityMessageMaker.AppendFormat(CultureInfo.Cu
rrentCulture, "{0}: ", (++counter).ToString(CultureInfo.CurrentCulture)); | |
230 securityMessageMaker.AppendLine(tempMessage); | |
231 securityErrorMessage = tempMessage; | |
232 } | |
233 | |
234 if(counter > 1) | |
235 { | |
236 securityErrorMessage = securityMessageMaker.ToSt
ring(); | |
237 } | |
238 | |
239 return String.IsNullOrEmpty(securityErrorMessage); | |
240 } | |
241 | |
242 /// <summary> | |
243 /// Checks if the project is safe with imports. The project file
is considered | |
244 /// unsafe if it contains any imports not registered in the safe
import regkey. | |
245 /// </summary> | |
246 /// <param name="securityErrorMessage">At return describes the r
eason why the projects is not considered safe.</param> | |
247 /// <returns>true if the project is safe regarding imports.</ret
urns> | |
248 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Desi
gn", "CA1021:AvoidOutParameters", MessageId = "0#", | |
249 Justification = "The error message needs to be an out pa
rameter. We are following here the Try... method patterns.")] | |
250 protected virtual bool IsProjectSafeWithImports(out string secur
ityErrorMessage) | |
251 { | |
252 securityErrorMessage = String.Empty; | |
253 | |
254 // Now get the directly imports and do the comparision. | |
255 string[] directImports = this.securityCheckHelper.GetDir
ectlyImportedProjects(this.projectShim); | |
256 if(directImports != null && directImports.Length > 0) | |
257 { | |
258 IList<string> safeImportList = ProjectSecurityCh
ecker.GetSafeImportList(); | |
259 | |
260 for(int i = 0; i < directImports.Length; i++) | |
261 { | |
262 string fileToCheck = directImports[i]; | |
263 if(!ProjectSecurityChecker.IsSafeImport(
safeImportList, fileToCheck)) | |
264 { | |
265 using(RegistryKey root = VSRegis
try.RegistryRoot(__VsLocalRegistryType.RegType_Configuration)) | |
266 { | |
267 securityErrorMessage = S
tring.Format(CultureInfo.CurrentCulture, SR.GetString(SR.DetailsImport, CultureI
nfo.CurrentUICulture), Path.GetFileName(this.projectShim.FullFileName), fileToCh
eck, Path.Combine(root.Name, SafeImportsSubkey)); | |
268 } | |
269 | |
270 return false; | |
271 } | |
272 } | |
273 } | |
274 | |
275 return true; | |
276 } | |
277 | |
278 | |
279 | |
280 /// <summary> | |
281 /// Checks if the project is safe regarding properties. | |
282 /// </summary> | |
283 /// <param name="securityErrorMessage">At return describes the r
eason why the projects is not considered safe.</param> | |
284 /// <returns>true if the project has only safe properties.</retu
rns> | |
285 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Desi
gn", "CA1021:AvoidOutParameters", MessageId = "0#", | |
286 Justification = "The error message needs to be an out pa
rameter. We are following here the Try... method patterns.")] | |
287 protected virtual bool IsProjectSafeWithProperties(out string se
curityErrorMessage) | |
288 { | |
289 securityErrorMessage = String.Empty; | |
290 | |
291 // Now ask the security check heper for the safe propert
ies. | |
292 string reasonForFailure; | |
293 bool isUserFile; | |
294 bool isProjectSafe = this.securityCheckHelper.IsProjectS
afe(ProjectSecurityChecker.DangerousPropertyProperty, | |
295
ProjectSecurityC
hecker.DefaultDangerousProperties, | |
296
this.projectShim
, | |
297
null, | |
298
SecurityCheckPas
s.Properties, | |
299
out reasonForFai
lure, | |
300
out isUserFile); | |
301 | |
302 if(!isProjectSafe) | |
303 { | |
304 securityErrorMessage = this.GetMessageString(rea
sonForFailure, SR.DetailsProperty); | |
305 } | |
306 | |
307 return isProjectSafe; | |
308 } | |
309 | |
310 /// <summary> | |
311 /// Checks if the project is safe regarding targets. | |
312 /// </summary> | |
313 /// <param name="securityErrorMessage">At return describes the r
eason why the projects is not considered safe.</param> | |
314 /// <returns>true if the project has only safe targets.</returns
> | |
315 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Desi
gn", "CA1021:AvoidOutParameters", MessageId = "0#", | |
316 Justification = "The error message needs to be an out pa
rameter. We are following here the Try... method patterns.")] | |
317 protected virtual bool IsProjectSafeWithTargets(out string secur
ityErrorMessage) | |
318 { | |
319 securityErrorMessage = String.Empty; | |
320 | |
321 // Now ask the security check heper for the safe targets
. | |
322 string reasonForFailure; | |
323 bool isUserFile; | |
324 bool isProjectSafe = this.securityCheckHelper.IsProjectS
afe(ProjectSecurityChecker.DangerousTargetProperty, | |
325
ProjectSecurityC
hecker.DefaultDangerousTargets, | |
326
this.projectShim
, | |
327
null, | |
328
SecurityCheckPas
s.Targets, | |
329
out reasonForFai
lure, | |
330
out isUserFile); | |
331 | |
332 if(!isProjectSafe) | |
333 { | |
334 securityErrorMessage = this.GetMessageString(rea
sonForFailure, SR.DetailsTarget); | |
335 } | |
336 | |
337 return isProjectSafe; | |
338 } | |
339 | |
340 /// <summary> | |
341 /// Checks if the project is safe regarding items. | |
342 /// </summary> | |
343 /// <param name="securityErrorMessage">At return describes the r
eason why the projects is not considered safe.</param> | |
344 /// <returns>true if the project has only safe items.</returns> | |
345 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Desi
gn", "CA1021:AvoidOutParameters", MessageId = "0#", | |
346 Justification = "The error message needs to be an out pa
rameter. We are following here the Try... method patterns.")] | |
347 protected virtual bool IsProjectSafeWithItems(out string securit
yErrorMessage) | |
348 { | |
349 securityErrorMessage = String.Empty; | |
350 | |
351 // Now ask the security check heper for the safe items. | |
352 string reasonForFailure; | |
353 bool isUserFile; | |
354 bool isProjectSafe = this.securityCheckHelper.IsProjectS
afe(ProjectSecurityChecker.DangerousItemsProperty, | |
355
ProjectSecurityC
hecker.DefaultDangerousItems, | |
356
this.projectShim
, | |
357
null, | |
358
SecurityCheckPas
s.Items, | |
359
out reasonForFai
lure, | |
360
out isUserFile); | |
361 | |
362 if(!isProjectSafe) | |
363 { | |
364 securityErrorMessage = this.GetMessageString(rea
sonForFailure, SR.DetailsItem); | |
365 } | |
366 | |
367 return isProjectSafe; | |
368 } | |
369 | |
370 /// <summary> | |
371 /// Checks if the project is safe with using tasks. | |
372 /// </summary> | |
373 /// <param name="securityErrorMessage">At return describes the r
eason why the projects is not considered safe.</param> | |
374 /// <returns>true if the project has no using tasks defined in t
he project file.</returns> | |
375 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Desi
gn", "CA1021:AvoidOutParameters", MessageId = "0#", | |
376 Justification = "The error message needs to be an out pa
rameter. We are following here the Try... method patterns.")] | |
377 protected virtual bool IsProjectSafeWithUsingTasks(out string se
curityErrorMessage) | |
378 { | |
379 securityErrorMessage = String.Empty; | |
380 | |
381 string[] usingTasks = this.securityCheckHelper.GetNonImp
ortedUsingTasks(this.projectShim); | |
382 | |
383 if(usingTasks != null && usingTasks.Length > 0) | |
384 { | |
385 securityErrorMessage = String.Format(CultureInfo
.CurrentCulture, SR.GetString(SR.DetailsUsingTask, CultureInfo.CurrentUICulture)
, Path.GetFileName(this.projectShim.FullFileName), usingTasks[0]); | |
386 return false; | |
387 } | |
388 | |
389 return true; | |
390 } | |
391 | |
392 /// <summary> | |
393 /// If the project contains the LoadTimeCheckItemsWithinProject
Cone property, the method verifies that all the items listed in there are within
the project cone. | |
394 /// Also checks that the project is not in Program Files or Win
dows if the property was there. | |
395 /// </summary> | |
396 /// <param name="securityErrorMessage">At return describes the r
eason why the projects is not considered safe.</param> | |
397 /// <returns>true if the project has no badly defined project it
ems.</returns> | |
398 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Desi
gn", "CA1021:AvoidOutParameters", MessageId = "0#", | |
399 Justification = "The error message needs to be an out pa
rameter. We are following here the Try... method patterns.")] | |
400 protected virtual bool CheckItemsLocation(out string securityErr
orMessage) | |
401 { | |
402 securityErrorMessage = String.Empty; | |
403 | |
404 // Get the <LoadTimeCheckItemLocation> property from the
project | |
405 string itemLocationProperty = this.projectShim.GetEvalua
tedProperty(ProjectSecurityChecker.CheckItemLocationProperty); | |
406 | |
407 if(String.IsNullOrEmpty(itemLocationProperty)) | |
408 { | |
409 return true; | |
410 } | |
411 | |
412 // Takes a semicolon separated list of entries, splits t
hem and puts them into a list with values trimmed. | |
413 string[] items = itemLocationProperty.Split(ProjectSecur
ityChecker.DangerousListSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyE
ntries); | |
414 | |
415 IList<string> itemsToCheck = new List<string>(); | |
416 foreach(string item in items) | |
417 { | |
418 itemsToCheck.Add(item.Trim()); | |
419 } | |
420 | |
421 // Now check the items for being defined in a safe locat
ion. | |
422 string reasonForFailure; | |
423 ItemSecurityChecker itemsSecurityChecker = new ItemSecur
ityChecker(this.serviceProvider, this.projectShim.FullFileName); | |
424 if(!itemsSecurityChecker.CheckItemsSecurity(this.project
Shim, itemsToCheck, out reasonForFailure)) | |
425 { | |
426 securityErrorMessage = String.Format(CultureInfo
.CurrentCulture, SR.GetString(SR.DetailsItemLocation, CultureInfo.CurrentUICultu
re), Path.GetFileName(this.projectShim.FullFileName), reasonForFailure); | |
427 return false; | |
428 } | |
429 | |
430 return true; | |
431 } | |
432 | |
433 | |
434 /// <summary> | |
435 /// The method that does the cleanup. | |
436 /// </summary> | |
437 /// <param name="disposing">true if called from IDispose.Dispose
; false if called from Finalizer.</param> | |
438 protected virtual void Dispose(bool disposing) | |
439 { | |
440 // Everybody can go here. | |
441 if(!this.isDisposed) | |
442 { | |
443 // Synchronize calls to the Dispose simultanious
ly. | |
444 lock(Mutex) | |
445 { | |
446 if(disposing) | |
447 { | |
448 this.projectShim.ParentEngine.Un
loadProject(this.projectShim); | |
449 } | |
450 | |
451 this.isDisposed = true; | |
452 } | |
453 } | |
454 } | |
455 #endregion | |
456 | |
457 #region helper methods | |
458 /// <summary> | |
459 /// Gets a message string that has an associated format with a r
eason for failure. | |
460 /// </summary> | |
461 /// <param name="reasonForFailure"></param> | |
462 /// <param name="resourceID"></param> | |
463 /// <returns></returns> | |
464 internal string GetMessageString(string reasonForFailure, string
resourceID) | |
465 { | |
466 Debug.Assert(!String.IsNullOrEmpty(reasonForFailure), "T
he reason for failure should not be empty or null"); | |
467 Debug.Assert(!String.IsNullOrEmpty(resourceID), "The res
ource id string cannot be empty"); | |
468 | |
469 return String.Format(CultureInfo.CurrentCulture, SR.GetS
tring(resourceID, Path.GetFileName(this.projectShim.FullFileName), reasonForFail
ure)); | |
470 } | |
471 | |
472 /// <summary> | |
473 /// Generates a format string that will be pushed to the More De
tailed dialog. | |
474 /// </summary> | |
475 /// <param name="securityMessageMaker">The Stringbuilder object
containing the formatted message.</param> | |
476 /// <param name="counter">The 'issue' number.</param> | |
477 /// <param name="securityErrorMessage">The message to format.</p
aram> | |
478 private static void FormatMessage(StringBuilder securityMessageM
aker, int counter, string securityErrorMessage) | |
479 { | |
480 securityMessageMaker.AppendFormat(CultureInfo.CurrentCul
ture, "{0}: ", counter.ToString(CultureInfo.CurrentCulture)); | |
481 securityMessageMaker.AppendLine(securityErrorMessage); | |
482 securityMessageMaker.Append(Environment.NewLine); | |
483 } | |
484 | |
485 /// <summary> | |
486 /// Returns a set of file info's describing the files in the Saf
eImports registry location. | |
487 /// </summary> | |
488 /// <returns>A set of FileInfo objects describing the files in t
he SafeImports location.</returns> | |
489 private static IList<string> GetSafeImportList() | |
490 { | |
491 List<string> importsList = new List<string>(); | |
492 | |
493 using(RegistryKey root = VSRegistry.RegistryRoot(__VsLoc
alRegistryType.RegType_Configuration)) | |
494 { | |
495 if(root != null) | |
496 { | |
497 using(RegistryKey key = root.OpenSubKey(
SafeImportsSubkey)) | |
498 { | |
499 if(key != null) | |
500 { | |
501 foreach(string value in
key.GetValueNames()) | |
502 { | |
503 string keyValue
= key.GetValue(value, String.Empty, RegistryValueOptions.None) as string; | |
504 // Make sure tha
t the environment variables are expanded. | |
505 keyValue = Syste
m.Environment.ExpandEnvironmentVariables(keyValue); | |
506 Uri uri; | |
507 if(!String.IsNul
lOrEmpty(keyValue) && Uri.TryCreate(keyValue, UriKind.Absolute, out uri) && uri.
IsAbsoluteUri) | |
508 { | |
509 importsL
ist.Add(keyValue); | |
510 } | |
511 } | |
512 } | |
513 } | |
514 } | |
515 } | |
516 | |
517 return importsList; | |
518 } | |
519 | |
520 /// <summary> | |
521 /// Checks if an import is a safe import. | |
522 /// </summary> | |
523 /// <param name="safeImportsList">A list of safe imports from te
h registry.</param> | |
524 /// <param name="fileToCheck">The file to check.</param> | |
525 /// <returns>true if the file to check can be found in the safe
import list</returns> | |
526 private static bool IsSafeImport(IList<string> safeImportsList,
string fileToCheck) | |
527 { | |
528 foreach(string safeImport in safeImportsList) | |
529 { | |
530 if(NativeMethods.IsSamePath(safeImport, fileToCh
eck)) | |
531 { | |
532 return true; | |
533 } | |
534 } | |
535 | |
536 return false; | |
537 } | |
538 #endregion | |
539 | |
540 #region nested types | |
541 /// <summary> | |
542 /// Class for checking that the items defined in LoadTimeCheckIt
emLocation are being defined in safe locations. | |
543 /// </summary> | |
544 private class ItemSecurityChecker | |
545 { | |
546 #region fields | |
547 /// <summary> | |
548 /// The associated service provider. | |
549 /// </summary> | |
550 private IServiceProvider serviceProvider; | |
551 | |
552 /// <summary> | |
553 /// The solutionFolder; | |
554 /// </summary> | |
555 private Uri solutionFolder; | |
556 | |
557 /// <summary> | |
558 /// The project folder | |
559 /// </summary> | |
560 private Uri projectFolder; | |
561 | |
562 /// <summary> | |
563 /// The set of special folders. | |
564 /// </summary> | |
565 private IList<Uri> specialFolders; | |
566 #endregion | |
567 | |
568 #region ctors | |
569 /// <summary> | |
570 /// Overloaded Constructor | |
571 /// </summary> | |
572 /// <param name="projectFilePath">path to the project fi
le</param> | |
573 /// <param name="serviceProvider">A service provider.</p
aram> | |
574 internal ItemSecurityChecker(IServiceProvider servicePro
vider, string projectFullPath) | |
575 { | |
576 this.serviceProvider = serviceProvider; | |
577 | |
578 // Initialize the project and solution folders. | |
579 this.SetProjectFolder(projectFullPath); | |
580 this.SetSolutionFolder(); | |
581 | |
582 // Set the special folders. Maybe this should be
a static. | |
583 this.specialFolders = ItemSecurityChecker.SetSpe
cialFolders(); | |
584 } | |
585 #endregion | |
586 | |
587 #region methods | |
588 /// <summary> | |
589 /// Checks whether a set of project items described by t
he LoadTimeCheckItemLocation are in a safe location. | |
590 /// </summary> | |
591 /// <param name="projectShim">The project shim containin
g the items to be checked.</param> | |
592 /// <param name="itemsToCheck">The list of items to chec
k if they are in the project cone.</param> | |
593 /// <param name="reasonForFailure">The reason for failur
e if any of the files fails</param> | |
594 /// <returns>true if all project items are in the projec
t cone. Otherwise false.</returns> | |
595 internal bool CheckItemsSecurity(ProjectShim projectShim
, IList<string> itemsToCheck, out string reasonForFailure) | |
596 { | |
597 reasonForFailure = String.Empty; | |
598 | |
599 // If nothing to check assume that everything is
ok. | |
600 if(itemsToCheck == null) | |
601 { | |
602 return true; | |
603 } | |
604 | |
605 Debug.Assert(projectShim != null, "Cannot check
the items if no project has been defined!"); | |
606 | |
607 foreach(string itemName in itemsToCheck) | |
608 { | |
609 BuildItemGroupShim group = projectShim.G
etEvaluatedItemsByNameIgnoringCondition(itemName); | |
610 if(group != null) | |
611 { | |
612 IEnumerator enumerator = group.G
etEnumerator(); | |
613 while(enumerator.MoveNext()) | |
614 { | |
615 BuildItemShim item = enu
merator.Current as BuildItemShim; | |
616 | |
617 string finalItem = item.
FinalItemSpec; | |
618 | |
619 if(!String.IsNullOrEmpty
(finalItem)) | |
620 { | |
621 // Perform the a
ctual check - start with normalizing the path. Relative paths | |
622 // should be tre
ated as relative to the project file. | |
623 string fullPath
= this.GetFullPath(finalItem); | |
624 | |
625 // If the fullpa
th of the item is suspiciously short do not check it. | |
626 if(fullPath.Leng
th >= 3) | |
627 { | |
628 Uri uri
= null; | |
629 | |
630 // If we
cannot create a uri from the item path return with the error | |
631 if(!Uri.
TryCreate(fullPath, UriKind.Absolute, out uri)) | |
632 { | |
633
reasonForFailure = fullPath; | |
634
return false; | |
635 } | |
636 | |
637 // Check
if the item points to a network share | |
638 if(uri.I
sUnc) | |
639 { | |
640
reasonForFailure = fullPath; | |
641
return false; | |
642 } | |
643 | |
644 // Check
if the item is located in a drive root directory | |
645 if(uri.S
egments.Length == 3 && uri.Segments[1] == ":" && uri.Segments[2][0] == Path.Dire
ctorySeparatorChar) | |
646 { | |
647
reasonForFailure = fullPath; | |
648
return false; | |
649 } | |
650 | |
651 //Check
if the item is not in a special folder. | |
652 foreach(
Uri specialFolder in this.specialFolders) | |
653 { | |
654
if(ItemSecurityChecker.IsItemInCone(uri, specialFolder)) | |
655
{ | |
656
reasonForFailure = fullPath; | |
657
return false; | |
658
} | |
659 } | |
660 } | |
661 else | |
662 { | |
663 reasonFo
rFailure = fullPath; | |
664 return f
alse; | |
665 } | |
666 } | |
667 } | |
668 } | |
669 } | |
670 | |
671 return true; | |
672 } | |
673 | |
674 | |
675 /// <summary> | |
676 /// Gets the list of special directories. This method sh
ould be optimized if called more then once. | |
677 /// </summary> | |
678 /// <returns>The list of special directories</returns> | |
679 private static IList<Uri> SetSpecialFolders() | |
680 { | |
681 string[] specialFolderArray = new string[5] | |
682 { | |
683 Environment.GetFolderPath(Environment.SpecialFolder.System), | |
684 Environment.GetFolderPath(Environment.SpecialFolder.ProgramFi
les), | |
685 Environment.GetFolderPath(Environment.SpecialFolder.Startup), | |
686 ItemSecurityChecker.GetSpecialDirectoryFromNative(NativeMetho
ds.ExtendedSpecialFolder.Windows), | |
687 ItemSecurityChecker.GetSpecialDirectoryFromNative(NativeMetho
ds.ExtendedSpecialFolder.CommonStartup) | |
688 }; | |
689 | |
690 List<Uri> specialFolders = new List<Uri>(5); | |
691 | |
692 // Add trailing backslash to the folders. | |
693 foreach(string specialFolder in specialFolderArr
ay) | |
694 { | |
695 string tempFolder = specialFolder; | |
696 if(!tempFolder.EndsWith("\\", StringComp
arison.Ordinal)) | |
697 { | |
698 tempFolder += "\\"; | |
699 } | |
700 | |
701 specialFolders.Add(new Uri(tempFolder)); | |
702 } | |
703 | |
704 return specialFolders; | |
705 } | |
706 | |
707 /// <summary> | |
708 /// Some special folders are not supported by System.Env
ironment.GetFolderPath. Get these special folders using p/invoke. | |
709 /// </summary> | |
710 /// <param name="specialFolder">The type of special fold
er to retrieve.</param> | |
711 /// <returns>The folder path</returns> | |
712 private static string GetSpecialDirectoryFromNative(Nati
veMethods.ExtendedSpecialFolder extendedSpecialFolder) | |
713 { | |
714 string specialFolder = null; | |
715 IntPtr buffer = IntPtr.Zero; | |
716 | |
717 // Demand Unmanaged code permission. It should b
e normal to demand UnmanagedCodePermission from an assembly integrating into VS. | |
718 new SecurityPermission(SecurityPermissionFlag.Un
managedCode).Demand(); | |
719 try | |
720 { | |
721 buffer = Marshal.AllocHGlobal((NativeMet
hods.MAX_PATH + 1) * 2); | |
722 IntPtr[] pathIdentifier = new IntPtr[1]; | |
723 | |
724 if(ErrorHandler.Succeeded(UnsafeNativeMe
thods.SHGetSpecialFolderLocation(IntPtr.Zero, (int)extendedSpecialFolder, pathId
entifier)) && UnsafeNativeMethods.SHGetPathFromIDList(pathIdentifier[0], buffer)
) | |
725 { | |
726 specialFolder = Marshal.PtrToStr
ingAuto(buffer); | |
727 } | |
728 } | |
729 finally | |
730 { | |
731 if(buffer != IntPtr.Zero) | |
732 { | |
733 Marshal.FreeHGlobal(buffer); | |
734 } | |
735 } | |
736 | |
737 | |
738 return specialFolder; | |
739 } | |
740 | |
741 | |
742 /// <summary> | |
743 /// Checks if the itemToCheck is in the cone of the base
Uri. | |
744 /// </summary> | |
745 /// <param name="itemToCheck">The item to check</param> | |
746 /// <param name="baseUri">The base to the item. This sho
uld define a folder.</param> | |
747 /// <returns>true if the item to check is in the cone of
the baseUri.</returns> | |
748 private static bool IsItemInCone(Uri itemToCheck, Uri ba
seUri) | |
749 { | |
750 Debug.Assert(itemToCheck != null && baseUri != n
ull, "Cannot check for items since the input is wrong"); | |
751 Debug.Assert(!NativeMethods.IsSamePath(Path.GetD
irectoryName(baseUri.LocalPath), baseUri.LocalPath), "The " + baseUri.LocalPath
+ " is not a folder!"); | |
752 | |
753 return (itemToCheck.IsFile && baseUri.IsFile && | |
754 String.Compare(itemToCheck.LocalPath, 0,
baseUri.LocalPath, 0, baseUri.LocalPath.Length, StringComparison.OrdinalIgnoreC
ase) == 0); | |
755 } | |
756 | |
757 /// <summary> | |
758 /// Sets the solution folder. | |
759 /// </summary> | |
760 private void SetSolutionFolder() | |
761 { | |
762 if(this.solutionFolder != null) | |
763 { | |
764 return; | |
765 } | |
766 | |
767 IVsSolution solution = this.serviceProvider.GetS
ervice(typeof(SVsSolution)) as IVsSolution; | |
768 Debug.Assert(solution != null, "Could not retrie
ve the solution service from the global service provider"); | |
769 | |
770 string solutionDirectory, solutionFile, userOpti
onsFile; | |
771 | |
772 // We do not want to throw. If we cannot set the
solution related constants we set them to empty string. | |
773 ErrorHandler.ThrowOnFailure(solution.GetSolution
Info(out solutionDirectory, out solutionFile, out userOptionsFile)); | |
774 | |
775 if(String.IsNullOrEmpty(solutionDirectory)) | |
776 { | |
777 return; | |
778 } | |
779 | |
780 // Make sure the solution dir ends with a backsl
ash | |
781 if(solutionDirectory[solutionDirectory.Length -
1] != Path.DirectorySeparatorChar) | |
782 { | |
783 solutionDirectory += Path.DirectorySepar
atorChar; | |
784 } | |
785 | |
786 Uri.TryCreate(solutionDirectory, UriKind.Absolut
e, out this.solutionFolder); | |
787 | |
788 Debug.Assert(this.solutionFolder != null, "Could
not create the Uri for the solution folder"); | |
789 } | |
790 | |
791 /// <summary> | |
792 /// Sets the project folder. | |
793 /// </summary> | |
794 /// <param name="projectFullPath">The path to the projec
t</param> | |
795 private void SetProjectFolder(string projectFullPath) | |
796 { | |
797 if(this.projectFolder != null) | |
798 { | |
799 return; | |
800 } | |
801 | |
802 string tempProjectFolder = Path.GetDirectoryName
(projectFullPath); | |
803 | |
804 // Make sure the project dir ends with a backsla
sh | |
805 if(!tempProjectFolder.EndsWith("\\", StringCompa
rison.Ordinal) && !tempProjectFolder.EndsWith("/", StringComparison.Ordinal)) | |
806 { | |
807 tempProjectFolder += "\\"; | |
808 } | |
809 | |
810 Uri.TryCreate(tempProjectFolder, UriKind.Absolut
e, out this.projectFolder); | |
811 | |
812 Debug.Assert(this.projectFolder != null, "Could
not create the Uri for the project folder"); | |
813 } | |
814 | |
815 /// <summary> | |
816 /// Gets the fullpath of an item. | |
817 /// Relative pathes are treated as relative to the proje
ct file. | |
818 /// </summary> | |
819 /// <param name="item">The item.</param> | |
820 /// <returns>The ful path of the item.</returns> | |
821 private string GetFullPath(string item) | |
822 { | |
823 Url url; | |
824 if(Path.IsPathRooted(item)) | |
825 { | |
826 // Use absolute path | |
827 url = new Microsoft.VisualStudio.Shell.U
rl(item); | |
828 } | |
829 else | |
830 { | |
831 // Path is relative, so make it relative
to project path | |
832 url = new Url(new Url(this.projectFolder
.LocalPath), item); | |
833 } | |
834 | |
835 return url.AbsoluteUrl; | |
836 } | |
837 #endregion | |
838 } | |
839 #endregion | |
840 } | |
841 } | |
OLD | NEW |