PageRenderTime 49ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/Tools/IronStudio/IronStudio/VisualStudio/Project/ProjectSecurityChecker.cs

http://github.com/IronLanguages/main
C# | 852 lines | 497 code | 127 blank | 228 comment | 71 complexity | 1e7ebc0ac417432e8a001b95bbba5af9 MD5 | raw file
Possible License(s): CPL-1.0, BSD-3-Clause, ISC, GPL-2.0, MPL-2.0-no-copyleft-exception
  1. /***************************************************************************
  2. Copyright (c) Microsoft Corporation. All rights reserved.
  3. This code is licensed under the Visual Studio SDK license terms.
  4. THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
  5. ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
  6. IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
  7. PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
  8. ***************************************************************************/
  9. extern alias Shell10;
  10. using System;
  11. using System.Collections;
  12. using System.Collections.Generic;
  13. using System.Diagnostics;
  14. using System.Globalization;
  15. using System.IO;
  16. using System.Runtime.InteropServices;
  17. using System.Security.Permissions;
  18. using System.Text;
  19. using Microsoft.VisualStudio;
  20. using Microsoft.VisualStudio.Shell;
  21. using Microsoft.VisualStudio.Shell.Interop;
  22. using Microsoft.Win32;
  23. namespace Microsoft.VisualStudio.Project
  24. {
  25. using Url = Shell10.Microsoft.VisualStudio.Shell.Url;
  26. /// <summary>
  27. /// Does security validation of a project before loading the project
  28. /// </summary>
  29. public class ProjectSecurityChecker : IDisposable
  30. {
  31. #region constants
  32. /// <summary>
  33. /// The dangereous target property.
  34. /// </summary>
  35. internal const string DangerousTargetProperty = "LoadTimeSensitiveTargets";
  36. /// <summary>
  37. /// The dangereous properties property.
  38. /// </summary>
  39. internal const string DangerousPropertyProperty = "LoadTimeSensitiveProperties";
  40. /// <summary>
  41. /// The dangereous items property.
  42. /// </summary>
  43. internal const string DangerousItemsProperty = "LoadTimeSensitiveItems";
  44. /// <summary>
  45. /// The check item locations property.
  46. /// </summary>
  47. internal const string CheckItemLocationProperty = "LoadTimeCheckItemLocation";
  48. /// <summary>
  49. /// The dangereous list item separator.
  50. /// </summary>
  51. internal const string DangerousListSeparator = ";";
  52. /// <summary>
  53. /// The project directory property.
  54. /// </summary>
  55. internal const string ProjectDirectoryProperty = "MSBuildProjectDirectory";
  56. /// <summary>
  57. /// The default dangereous properties.
  58. /// </summary>
  59. internal const string DefaultDangerousProperties = "LoadTimeSensitiveTargets;LoadTimeSensitiveProperties;LoadTimeSensitiveItems;LoadTimeCheckItemLocation;";
  60. /// <summary>
  61. /// The default dangereous targets.
  62. /// </summary>
  63. internal const string DefaultDangerousTargets = "Compile;GetFrameworkPaths;AllProjectOutputGroups;AllProjectOutputGroupsDependencies;CopyRunEnvironmentFiles;ResolveComReferences;ResolveAssemblyReferences;ResolveNativeReferences;";
  64. /// <summary>
  65. /// The default dangereous items.
  66. /// </summary>
  67. internal const string DefaultDangerousItems = ";";
  68. /// <summary>
  69. /// Defined the safe imports subkey in the registry.
  70. /// </summary>
  71. internal const string SafeImportsSubkey = @"MSBuild\SafeImports";
  72. #endregion
  73. #region fields
  74. /// <summary>
  75. /// Defines an object that will be a mutex for this object for synchronizing thread calls.
  76. /// </summary>
  77. private static volatile object Mutex = new object();
  78. /// <summary>
  79. /// Flag determining if the object has been disposed.
  80. /// </summary>
  81. private bool isDisposed;
  82. /// <summary>
  83. /// The associated project shim for the project file
  84. /// </summary>
  85. private ProjectShim projectShim;
  86. /// <summary>
  87. /// The security check helper object used to call out to do necessary security checkings.
  88. /// </summary>
  89. private SecurityCheckHelper securityCheckHelper = new SecurityCheckHelper();
  90. /// <summary>
  91. /// The associated service provider.
  92. /// </summary>
  93. private IServiceProvider serviceProvider;
  94. #endregion
  95. #region properties
  96. /// <summary>
  97. /// The associated project shim for the project file
  98. /// </summary>
  99. /// <devremark>The project shim is made internal in order to be able to be passed to the user project.</devremark>
  100. internal protected ProjectShim ProjectShim
  101. {
  102. get { return this.projectShim; }
  103. }
  104. /// <summary>
  105. /// The security check helper that will be used to perform the necessary checkings.
  106. /// </summary>
  107. protected SecurityCheckHelper SecurityCheckHelper
  108. {
  109. get { return this.securityCheckHelper; }
  110. }
  111. /// <summary>
  112. /// The associated service provider.
  113. /// </summary>
  114. protected IServiceProvider ServiceProvider
  115. {
  116. get
  117. {
  118. return this.serviceProvider;
  119. }
  120. }
  121. #endregion
  122. #region ctors
  123. /// <summary>
  124. /// Overloaded Constructor
  125. /// </summary>
  126. /// <param name="projectFilePath">path to the project file</param>
  127. /// <param name="serviceProvider">A service provider.</param>
  128. public ProjectSecurityChecker(IServiceProvider serviceProvider, string projectFilePath)
  129. {
  130. if(serviceProvider == null)
  131. {
  132. throw new ArgumentNullException("serviceProvider");
  133. }
  134. if(String.IsNullOrEmpty(projectFilePath))
  135. {
  136. throw new ArgumentException(SR.GetString(SR.ParameterCannotBeNullOrEmpty, CultureInfo.CurrentUICulture), "projectFilePath");
  137. }
  138. this.serviceProvider = serviceProvider;
  139. // Instantiate a new project shim that we are going to use for security checkings.
  140. EngineShim engine = new EngineShim();
  141. this.projectShim = engine.CreateNewProject();
  142. this.projectShim.Load(projectFilePath);
  143. }
  144. #endregion
  145. #region IDisposable Members
  146. /// <summary>
  147. /// The IDispose interface Dispose method for disposing the object determinastically.
  148. /// </summary>
  149. public void Dispose()
  150. {
  151. this.Dispose(true);
  152. GC.SuppressFinalize(this);
  153. }
  154. #endregion
  155. #region virtual methods
  156. /// <summary>
  157. /// Check if the project is safe at load/design time
  158. /// </summary>
  159. /// <param name="securityErrorMessage">If the project is not safe contains an error message, describing the reason.</param>
  160. /// <returns>true if the project is safe, false otherwise</returns>
  161. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
  162. Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
  163. public virtual bool IsProjectSafeAtLoadTime(out string securityErrorMessage)
  164. {
  165. securityErrorMessage = String.Empty;
  166. StringBuilder securityMessageMaker = new StringBuilder();
  167. int counter = 0;
  168. string tempMessage;
  169. // STEP 1: Check direct imports.
  170. if(!this.IsProjectSafeWithImports(out tempMessage))
  171. {
  172. ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
  173. securityErrorMessage = tempMessage;
  174. }
  175. // STEP 2: Check dangerous properties
  176. if(!this.IsProjectSafeWithProperties(out tempMessage))
  177. {
  178. ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
  179. securityErrorMessage = tempMessage;
  180. }
  181. // STEP 3: Check dangerous targets
  182. if(!this.IsProjectSafeWithTargets(out tempMessage))
  183. {
  184. ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
  185. securityErrorMessage = tempMessage;
  186. }
  187. // STEP 4: Check dangerous items
  188. if(!this.IsProjectSafeWithItems(out tempMessage))
  189. {
  190. ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
  191. securityErrorMessage = tempMessage;
  192. }
  193. // STEP 5: Check UsingTask tasks
  194. if(!this.IsProjectSafeWithUsingTasks(out tempMessage))
  195. {
  196. ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
  197. securityErrorMessage = tempMessage;
  198. }
  199. // STEP 6: Check for items defined within the LoadTimeCheckItemLocation, whether they are defined in safe locations
  200. if(!this.CheckItemsLocation(out tempMessage))
  201. {
  202. securityMessageMaker.AppendFormat(CultureInfo.CurrentCulture, "{0}: ", (++counter).ToString(CultureInfo.CurrentCulture));
  203. securityMessageMaker.AppendLine(tempMessage);
  204. securityErrorMessage = tempMessage;
  205. }
  206. if(counter > 1)
  207. {
  208. securityErrorMessage = securityMessageMaker.ToString();
  209. }
  210. return String.IsNullOrEmpty(securityErrorMessage);
  211. }
  212. /// <summary>
  213. /// Checks if the project is safe with imports. The project file is considered
  214. /// unsafe if it contains any imports not registered in the safe import regkey.
  215. /// </summary>
  216. /// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
  217. /// <returns>true if the project is safe regarding imports.</returns>
  218. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
  219. Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
  220. protected virtual bool IsProjectSafeWithImports(out string securityErrorMessage)
  221. {
  222. securityErrorMessage = String.Empty;
  223. // Now get the directly imports and do the comparision.
  224. string[] directImports = this.securityCheckHelper.GetDirectlyImportedProjects(this.projectShim);
  225. if(directImports != null && directImports.Length > 0)
  226. {
  227. IList<string> safeImportList = ProjectSecurityChecker.GetSafeImportList();
  228. for(int i = 0; i < directImports.Length; i++)
  229. {
  230. string fileToCheck = directImports[i];
  231. if(!ProjectSecurityChecker.IsSafeImport(safeImportList, fileToCheck))
  232. {
  233. using(RegistryKey root = Shell10.Microsoft.VisualStudio.Shell.VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration))
  234. {
  235. securityErrorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.DetailsImport, CultureInfo.CurrentUICulture), Path.GetFileName(this.projectShim.FullFileName), fileToCheck, Path.Combine(root.Name, SafeImportsSubkey));
  236. }
  237. return false;
  238. }
  239. }
  240. }
  241. return true;
  242. }
  243. /// <summary>
  244. /// Checks if the project is safe regarding properties.
  245. /// </summary>
  246. /// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
  247. /// <returns>true if the project has only safe properties.</returns>
  248. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
  249. Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
  250. protected virtual bool IsProjectSafeWithProperties(out string securityErrorMessage)
  251. {
  252. securityErrorMessage = String.Empty;
  253. // Now ask the security check heper for the safe properties.
  254. string reasonForFailure;
  255. bool isUserFile;
  256. bool isProjectSafe = this.securityCheckHelper.IsProjectSafe(ProjectSecurityChecker.DangerousPropertyProperty,
  257. ProjectSecurityChecker.DefaultDangerousProperties,
  258. this.projectShim,
  259. null,
  260. SecurityCheckPass.Properties,
  261. out reasonForFailure,
  262. out isUserFile);
  263. if(!isProjectSafe)
  264. {
  265. securityErrorMessage = this.GetMessageString(reasonForFailure, SR.DetailsProperty);
  266. }
  267. return isProjectSafe;
  268. }
  269. /// <summary>
  270. /// Checks if the project is safe regarding targets.
  271. /// </summary>
  272. /// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
  273. /// <returns>true if the project has only safe targets.</returns>
  274. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
  275. Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
  276. protected virtual bool IsProjectSafeWithTargets(out string securityErrorMessage)
  277. {
  278. securityErrorMessage = String.Empty;
  279. // Now ask the security check heper for the safe targets.
  280. string reasonForFailure;
  281. bool isUserFile;
  282. bool isProjectSafe = this.securityCheckHelper.IsProjectSafe(ProjectSecurityChecker.DangerousTargetProperty,
  283. ProjectSecurityChecker.DefaultDangerousTargets,
  284. this.projectShim,
  285. null,
  286. SecurityCheckPass.Targets,
  287. out reasonForFailure,
  288. out isUserFile);
  289. if(!isProjectSafe)
  290. {
  291. securityErrorMessage = this.GetMessageString(reasonForFailure, SR.DetailsTarget);
  292. }
  293. return isProjectSafe;
  294. }
  295. /// <summary>
  296. /// Checks if the project is safe regarding items.
  297. /// </summary>
  298. /// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
  299. /// <returns>true if the project has only safe items.</returns>
  300. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
  301. Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
  302. protected virtual bool IsProjectSafeWithItems(out string securityErrorMessage)
  303. {
  304. securityErrorMessage = String.Empty;
  305. // Now ask the security check heper for the safe items.
  306. string reasonForFailure;
  307. bool isUserFile;
  308. bool isProjectSafe = this.securityCheckHelper.IsProjectSafe(ProjectSecurityChecker.DangerousItemsProperty,
  309. ProjectSecurityChecker.DefaultDangerousItems,
  310. this.projectShim,
  311. null,
  312. SecurityCheckPass.Items,
  313. out reasonForFailure,
  314. out isUserFile);
  315. if(!isProjectSafe)
  316. {
  317. securityErrorMessage = this.GetMessageString(reasonForFailure, SR.DetailsItem);
  318. }
  319. return isProjectSafe;
  320. }
  321. /// <summary>
  322. /// Checks if the project is safe with using tasks.
  323. /// </summary>
  324. /// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
  325. /// <returns>true if the project has no using tasks defined in the project file.</returns>
  326. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
  327. Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
  328. protected virtual bool IsProjectSafeWithUsingTasks(out string securityErrorMessage)
  329. {
  330. securityErrorMessage = String.Empty;
  331. string[] usingTasks = this.securityCheckHelper.GetNonImportedUsingTasks(this.projectShim);
  332. if(usingTasks != null && usingTasks.Length > 0)
  333. {
  334. securityErrorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.DetailsUsingTask, CultureInfo.CurrentUICulture), Path.GetFileName(this.projectShim.FullFileName), usingTasks[0]);
  335. return false;
  336. }
  337. return true;
  338. }
  339. /// <summary>
  340. /// If the project contains the LoadTimeCheckItemsWithinProjectCone property, the method verifies that all the items listed in there are within the project cone.
  341. /// Also checks that the project is not in Program Files or Windows if the property was there.
  342. /// </summary>
  343. /// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
  344. /// <returns>true if the project has no badly defined project items.</returns>
  345. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
  346. Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
  347. protected virtual bool CheckItemsLocation(out string securityErrorMessage)
  348. {
  349. securityErrorMessage = String.Empty;
  350. // Get the <LoadTimeCheckItemLocation> property from the project
  351. string itemLocationProperty = this.projectShim.GetEvaluatedProperty(ProjectSecurityChecker.CheckItemLocationProperty);
  352. if(String.IsNullOrEmpty(itemLocationProperty))
  353. {
  354. return true;
  355. }
  356. // Takes a semicolon separated list of entries, splits them and puts them into a list with values trimmed.
  357. string[] items = itemLocationProperty.Split(ProjectSecurityChecker.DangerousListSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
  358. IList<string> itemsToCheck = new List<string>();
  359. foreach(string item in items)
  360. {
  361. itemsToCheck.Add(item.Trim());
  362. }
  363. // Now check the items for being defined in a safe location.
  364. string reasonForFailure;
  365. ItemSecurityChecker itemsSecurityChecker = new ItemSecurityChecker(this.serviceProvider, this.projectShim.FullFileName);
  366. if(!itemsSecurityChecker.CheckItemsSecurity(this.projectShim, itemsToCheck, out reasonForFailure))
  367. {
  368. securityErrorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.DetailsItemLocation, CultureInfo.CurrentUICulture), Path.GetFileName(this.projectShim.FullFileName), reasonForFailure);
  369. return false;
  370. }
  371. return true;
  372. }
  373. /// <summary>
  374. /// The method that does the cleanup.
  375. /// </summary>
  376. /// <param name="disposing">true if called from IDispose.Dispose; false if called from Finalizer.</param>
  377. protected virtual void Dispose(bool disposing)
  378. {
  379. // Everybody can go here.
  380. if(!this.isDisposed)
  381. {
  382. // Synchronize calls to the Dispose simultaniously.
  383. lock(Mutex)
  384. {
  385. if(disposing)
  386. {
  387. this.projectShim.ParentEngine.UnloadProject(this.projectShim);
  388. }
  389. this.isDisposed = true;
  390. }
  391. }
  392. }
  393. #endregion
  394. #region helper methods
  395. /// <summary>
  396. /// Gets a message string that has an associated format with a reason for failure.
  397. /// </summary>
  398. /// <param name="reasonForFailure"></param>
  399. /// <param name="resourceID"></param>
  400. /// <returns></returns>
  401. internal string GetMessageString(string reasonForFailure, string resourceID)
  402. {
  403. Debug.Assert(!String.IsNullOrEmpty(reasonForFailure), "The reason for failure should not be empty or null");
  404. Debug.Assert(!String.IsNullOrEmpty(resourceID), "The resource id string cannot be empty");
  405. return String.Format(CultureInfo.CurrentCulture, SR.GetString(resourceID, Path.GetFileName(this.projectShim.FullFileName), reasonForFailure));
  406. }
  407. /// <summary>
  408. /// Generates a format string that will be pushed to the More Detailed dialog.
  409. /// </summary>
  410. /// <param name="securityMessageMaker">The Stringbuilder object containing the formatted message.</param>
  411. /// <param name="counter">The 'issue' number.</param>
  412. /// <param name="securityErrorMessage">The message to format.</param>
  413. private static void FormatMessage(StringBuilder securityMessageMaker, int counter, string securityErrorMessage)
  414. {
  415. securityMessageMaker.AppendFormat(CultureInfo.CurrentCulture, "{0}: ", counter.ToString(CultureInfo.CurrentCulture));
  416. securityMessageMaker.AppendLine(securityErrorMessage);
  417. securityMessageMaker.Append(Environment.NewLine);
  418. }
  419. /// <summary>
  420. /// Returns a set of file info's describing the files in the SafeImports registry location.
  421. /// </summary>
  422. /// <returns>A set of FileInfo objects describing the files in the SafeImports location.</returns>
  423. private static IList<string> GetSafeImportList()
  424. {
  425. List<string> importsList = new List<string>();
  426. using(RegistryKey root = Shell10.Microsoft.VisualStudio.Shell.VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration))
  427. {
  428. if(root != null)
  429. {
  430. using(RegistryKey key = root.OpenSubKey(SafeImportsSubkey))
  431. {
  432. if(key != null)
  433. {
  434. foreach(string value in key.GetValueNames())
  435. {
  436. string keyValue = key.GetValue(value, String.Empty, RegistryValueOptions.None) as string;
  437. // Make sure that the environment variables are expanded.
  438. keyValue = System.Environment.ExpandEnvironmentVariables(keyValue);
  439. Uri uri;
  440. if(!String.IsNullOrEmpty(keyValue) && Uri.TryCreate(keyValue, UriKind.Absolute, out uri) && uri.IsAbsoluteUri)
  441. {
  442. importsList.Add(keyValue);
  443. }
  444. }
  445. }
  446. }
  447. }
  448. }
  449. return importsList;
  450. }
  451. /// <summary>
  452. /// Checks if an import is a safe import.
  453. /// </summary>
  454. /// <param name="safeImportsList">A list of safe imports from teh registry.</param>
  455. /// <param name="fileToCheck">The file to check.</param>
  456. /// <returns>true if the file to check can be found in the safe import list</returns>
  457. private static bool IsSafeImport(IList<string> safeImportsList, string fileToCheck)
  458. {
  459. foreach(string safeImport in safeImportsList)
  460. {
  461. if(NativeMethods.IsSamePath(safeImport, fileToCheck))
  462. {
  463. return true;
  464. }
  465. }
  466. return false;
  467. }
  468. #endregion
  469. #region nested types
  470. /// <summary>
  471. /// Class for checking that the items defined in LoadTimeCheckItemLocation are being defined in safe locations.
  472. /// </summary>
  473. private class ItemSecurityChecker
  474. {
  475. #region fields
  476. /// <summary>
  477. /// The associated service provider.
  478. /// </summary>
  479. private IServiceProvider serviceProvider;
  480. /// <summary>
  481. /// The solutionFolder;
  482. /// </summary>
  483. private Uri solutionFolder;
  484. /// <summary>
  485. /// The project folder
  486. /// </summary>
  487. private Uri projectFolder;
  488. /// <summary>
  489. /// The set of special folders.
  490. /// </summary>
  491. private IList<Uri> specialFolders;
  492. #endregion
  493. #region ctors
  494. /// <summary>
  495. /// Overloaded Constructor
  496. /// </summary>
  497. /// <param name="projectFilePath">path to the project file</param>
  498. /// <param name="serviceProvider">A service provider.</param>
  499. internal ItemSecurityChecker(IServiceProvider serviceProvider, string projectFullPath)
  500. {
  501. this.serviceProvider = serviceProvider;
  502. // Initialize the project and solution folders.
  503. this.SetProjectFolder(projectFullPath);
  504. this.SetSolutionFolder();
  505. // Set the special folders. Maybe this should be a static.
  506. this.specialFolders = ItemSecurityChecker.SetSpecialFolders();
  507. }
  508. #endregion
  509. #region methods
  510. /// <summary>
  511. /// Checks whether a set of project items described by the LoadTimeCheckItemLocation are in a safe location.
  512. /// </summary>
  513. /// <param name="projectShim">The project shim containing the items to be checked.</param>
  514. /// <param name="itemsToCheck">The list of items to check if they are in the project cone.</param>
  515. /// <param name="reasonForFailure">The reason for failure if any of the files fails</param>
  516. /// <returns>true if all project items are in the project cone. Otherwise false.</returns>
  517. internal bool CheckItemsSecurity(ProjectShim projectShim, IList<string> itemsToCheck, out string reasonForFailure)
  518. {
  519. reasonForFailure = String.Empty;
  520. // If nothing to check assume that everything is ok.
  521. if(itemsToCheck == null)
  522. {
  523. return true;
  524. }
  525. Debug.Assert(projectShim != null, "Cannot check the items if no project has been defined!");
  526. foreach(string itemName in itemsToCheck)
  527. {
  528. BuildItemGroupShim group = projectShim.GetEvaluatedItemsByNameIgnoringCondition(itemName);
  529. if(group != null)
  530. {
  531. IEnumerator enumerator = group.GetEnumerator();
  532. while(enumerator.MoveNext())
  533. {
  534. BuildItemShim item = enumerator.Current as BuildItemShim;
  535. string finalItem = item.FinalItemSpec;
  536. if(!String.IsNullOrEmpty(finalItem))
  537. {
  538. // Perform the actual check - start with normalizing the path. Relative paths
  539. // should be treated as relative to the project file.
  540. string fullPath = this.GetFullPath(finalItem);
  541. // If the fullpath of the item is suspiciously short do not check it.
  542. if(fullPath.Length >= 3)
  543. {
  544. Uri uri = null;
  545. // If we cannot create a uri from the item path return with the error
  546. if(!Uri.TryCreate(fullPath, UriKind.Absolute, out uri))
  547. {
  548. reasonForFailure = fullPath;
  549. return false;
  550. }
  551. // Check if the item points to a network share
  552. if(uri.IsUnc)
  553. {
  554. reasonForFailure = fullPath;
  555. return false;
  556. }
  557. // Check if the item is located in a drive root directory
  558. if(uri.Segments.Length == 3 && uri.Segments[1] == ":" && uri.Segments[2][0] == Path.DirectorySeparatorChar)
  559. {
  560. reasonForFailure = fullPath;
  561. return false;
  562. }
  563. //Check if the item is not in a special folder.
  564. foreach(Uri specialFolder in this.specialFolders)
  565. {
  566. if(ItemSecurityChecker.IsItemInCone(uri, specialFolder))
  567. {
  568. reasonForFailure = fullPath;
  569. return false;
  570. }
  571. }
  572. }
  573. else
  574. {
  575. reasonForFailure = fullPath;
  576. return false;
  577. }
  578. }
  579. }
  580. }
  581. }
  582. return true;
  583. }
  584. /// <summary>
  585. /// Gets the list of special directories. This method should be optimized if called more then once.
  586. /// </summary>
  587. /// <returns>The list of special directories</returns>
  588. private static IList<Uri> SetSpecialFolders()
  589. {
  590. string[] specialFolderArray = new string[5]
  591. {
  592. Environment.GetFolderPath(Environment.SpecialFolder.System),
  593. Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
  594. Environment.GetFolderPath(Environment.SpecialFolder.Startup),
  595. ItemSecurityChecker.GetSpecialDirectoryFromNative(NativeMethods.ExtendedSpecialFolder.Windows),
  596. ItemSecurityChecker.GetSpecialDirectoryFromNative(NativeMethods.ExtendedSpecialFolder.CommonStartup)
  597. };
  598. List<Uri> specialFolders = new List<Uri>(5);
  599. // Add trailing backslash to the folders.
  600. foreach(string specialFolder in specialFolderArray)
  601. {
  602. string tempFolder = specialFolder;
  603. if(!tempFolder.EndsWith("\\", StringComparison.Ordinal))
  604. {
  605. tempFolder += "\\";
  606. }
  607. specialFolders.Add(new Uri(tempFolder));
  608. }
  609. return specialFolders;
  610. }
  611. /// <summary>
  612. /// Some special folders are not supported by System.Environment.GetFolderPath. Get these special folders using p/invoke.
  613. /// </summary>
  614. /// <param name="specialFolder">The type of special folder to retrieve.</param>
  615. /// <returns>The folder path</returns>
  616. private static string GetSpecialDirectoryFromNative(NativeMethods.ExtendedSpecialFolder extendedSpecialFolder)
  617. {
  618. string specialFolder = null;
  619. IntPtr buffer = IntPtr.Zero;
  620. // Demand Unmanaged code permission. It should be normal to demand UnmanagedCodePermission from an assembly integrating into VS.
  621. new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
  622. try
  623. {
  624. buffer = Marshal.AllocHGlobal((NativeMethods.MAX_PATH + 1) * 2);
  625. IntPtr[] pathIdentifier = new IntPtr[1];
  626. if(ErrorHandler.Succeeded(UnsafeNativeMethods.SHGetSpecialFolderLocation(IntPtr.Zero, (int)extendedSpecialFolder, pathIdentifier)) && UnsafeNativeMethods.SHGetPathFromIDList(pathIdentifier[0], buffer))
  627. {
  628. specialFolder = Marshal.PtrToStringAuto(buffer);
  629. }
  630. }
  631. finally
  632. {
  633. if(buffer != IntPtr.Zero)
  634. {
  635. Marshal.FreeHGlobal(buffer);
  636. }
  637. }
  638. return specialFolder;
  639. }
  640. /// <summary>
  641. /// Checks if the itemToCheck is in the cone of the baseUri.
  642. /// </summary>
  643. /// <param name="itemToCheck">The item to check</param>
  644. /// <param name="baseUri">The base to the item. This should define a folder.</param>
  645. /// <returns>true if the item to check is in the cone of the baseUri.</returns>
  646. private static bool IsItemInCone(Uri itemToCheck, Uri baseUri)
  647. {
  648. Debug.Assert(itemToCheck != null && baseUri != null, "Cannot check for items since the input is wrong");
  649. Debug.Assert(!NativeMethods.IsSamePath(Path.GetDirectoryName(baseUri.LocalPath), baseUri.LocalPath), "The " + baseUri.LocalPath + " is not a folder!");
  650. return (itemToCheck.IsFile && baseUri.IsFile &&
  651. String.Compare(itemToCheck.LocalPath, 0, baseUri.LocalPath, 0, baseUri.LocalPath.Length, StringComparison.OrdinalIgnoreCase) == 0);
  652. }
  653. /// <summary>
  654. /// Sets the solution folder.
  655. /// </summary>
  656. private void SetSolutionFolder()
  657. {
  658. if(this.solutionFolder != null)
  659. {
  660. return;
  661. }
  662. IVsSolution solution = this.serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution;
  663. Debug.Assert(solution != null, "Could not retrieve the solution service from the global service provider");
  664. string solutionDirectory, solutionFile, userOptionsFile;
  665. // We do not want to throw. If we cannot set the solution related constants we set them to empty string.
  666. ErrorHandler.ThrowOnFailure(solution.GetSolutionInfo(out solutionDirectory, out solutionFile, out userOptionsFile));
  667. if(String.IsNullOrEmpty(solutionDirectory))
  668. {
  669. return;
  670. }
  671. // Make sure the solution dir ends with a backslash
  672. if(solutionDirectory[solutionDirectory.Length - 1] != Path.DirectorySeparatorChar)
  673. {
  674. solutionDirectory += Path.DirectorySeparatorChar;
  675. }
  676. Uri.TryCreate(solutionDirectory, UriKind.Absolute, out this.solutionFolder);
  677. Debug.Assert(this.solutionFolder != null, "Could not create the Uri for the solution folder");
  678. }
  679. /// <summary>
  680. /// Sets the project folder.
  681. /// </summary>
  682. /// <param name="projectFullPath">The path to the project</param>
  683. private void SetProjectFolder(string projectFullPath)
  684. {
  685. if(this.projectFolder != null)
  686. {
  687. return;
  688. }
  689. string tempProjectFolder = Path.GetDirectoryName(projectFullPath);
  690. // Make sure the project dir ends with a backslash
  691. if(!tempProjectFolder.EndsWith("\\", StringComparison.Ordinal) && !tempProjectFolder.EndsWith("/", StringComparison.Ordinal))
  692. {
  693. tempProjectFolder += "\\";
  694. }
  695. Uri.TryCreate(tempProjectFolder, UriKind.Absolute, out this.projectFolder);
  696. Debug.Assert(this.projectFolder != null, "Could not create the Uri for the project folder");
  697. }
  698. /// <summary>
  699. /// Gets the fullpath of an item.
  700. /// Relative pathes are treated as relative to the project file.
  701. /// </summary>
  702. /// <param name="item">The item.</param>
  703. /// <returns>The ful path of the item.</returns>
  704. private string GetFullPath(string item)
  705. {
  706. Url url;
  707. if(Path.IsPathRooted(item))
  708. {
  709. // Use absolute path
  710. url = new Url(item);
  711. }
  712. else
  713. {
  714. // Path is relative, so make it relative to project path
  715. url = new Url(new Url(this.projectFolder.LocalPath), item);
  716. }
  717. return url.AbsoluteUrl;
  718. }
  719. #endregion
  720. }
  721. #endregion
  722. }
  723. }