mirror of
https://github.com/danbulant/Cosmos
synced 2026-05-21 05:18:38 +00:00
841 lines
30 KiB
C#
841 lines
30 KiB
C#
/// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.Permissions;
|
|
using System.Text;
|
|
using Microsoft.VisualStudio;
|
|
using Microsoft.VisualStudio.Build.ComInteropWrapper;
|
|
using Microsoft.VisualStudio.Shell;
|
|
using Microsoft.VisualStudio.Shell.Interop;
|
|
using Microsoft.Win32;
|
|
|
|
namespace Microsoft.VisualStudio.Project
|
|
{
|
|
/// <summary>
|
|
/// Does security validation of a project before loading the project
|
|
/// </summary>
|
|
public class ProjectSecurityChecker : IDisposable
|
|
{
|
|
#region constants
|
|
/// <summary>
|
|
/// The dangereous target property.
|
|
/// </summary>
|
|
internal const string DangerousTargetProperty = "LoadTimeSensitiveTargets";
|
|
|
|
/// <summary>
|
|
/// The dangereous properties property.
|
|
/// </summary>
|
|
internal const string DangerousPropertyProperty = "LoadTimeSensitiveProperties";
|
|
|
|
/// <summary>
|
|
/// The dangereous items property.
|
|
/// </summary>
|
|
internal const string DangerousItemsProperty = "LoadTimeSensitiveItems";
|
|
|
|
/// <summary>
|
|
/// The check item locations property.
|
|
/// </summary>
|
|
internal const string CheckItemLocationProperty = "LoadTimeCheckItemLocation";
|
|
|
|
/// <summary>
|
|
/// The dangereous list item separator.
|
|
/// </summary>
|
|
internal const string DangerousListSeparator = ";";
|
|
|
|
/// <summary>
|
|
/// The project directory property.
|
|
/// </summary>
|
|
internal const string ProjectDirectoryProperty = "MSBuildProjectDirectory";
|
|
|
|
/// <summary>
|
|
/// The default dangereous properties.
|
|
/// </summary>
|
|
internal const string DefaultDangerousProperties = "LoadTimeSensitiveTargets;LoadTimeSensitiveProperties;LoadTimeSensitiveItems;LoadTimeCheckItemLocation;";
|
|
|
|
/// <summary>
|
|
/// The default dangereous targets.
|
|
/// </summary>
|
|
internal const string DefaultDangerousTargets = "Compile;GetFrameworkPaths;AllProjectOutputGroups;AllProjectOutputGroupsDependencies;CopyRunEnvironmentFiles;ResolveComReferences;ResolveAssemblyReferences;ResolveNativeReferences;";
|
|
|
|
/// <summary>
|
|
/// The default dangereous items.
|
|
/// </summary>
|
|
internal const string DefaultDangerousItems = ";";
|
|
|
|
/// <summary>
|
|
/// Defined the safe imports subkey in the registry.
|
|
/// </summary>
|
|
internal const string SafeImportsSubkey = @"MSBuild\SafeImports";
|
|
#endregion
|
|
|
|
#region fields
|
|
/// <summary>
|
|
/// Defines an object that will be a mutex for this object for synchronizing thread calls.
|
|
/// </summary>
|
|
private static volatile object Mutex = new object();
|
|
|
|
/// <summary>
|
|
/// Flag determining if the object has been disposed.
|
|
/// </summary>
|
|
private bool isDisposed;
|
|
|
|
/// <summary>
|
|
/// The associated project shim for the project file
|
|
/// </summary>
|
|
private ProjectShim projectShim;
|
|
|
|
/// <summary>
|
|
/// The security check helper object used to call out to do necessary security checkings.
|
|
/// </summary>
|
|
private SecurityCheckHelper securityCheckHelper = new SecurityCheckHelper();
|
|
|
|
/// <summary>
|
|
/// The associated service provider.
|
|
/// </summary>
|
|
private IServiceProvider serviceProvider;
|
|
|
|
#endregion
|
|
|
|
#region properties
|
|
/// <summary>
|
|
/// The associated project shim for the project file
|
|
/// </summary>
|
|
/// <devremark>The project shim is made internal in order to be able to be passed to the user project.</devremark>
|
|
internal protected ProjectShim ProjectShim
|
|
{
|
|
get { return this.projectShim; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The security check helper that will be used to perform the necessary checkings.
|
|
/// </summary>
|
|
protected SecurityCheckHelper SecurityCheckHelper
|
|
{
|
|
get { return this.securityCheckHelper; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The associated service provider.
|
|
/// </summary>
|
|
protected IServiceProvider ServiceProvider
|
|
{
|
|
get
|
|
{
|
|
return this.serviceProvider;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region ctors
|
|
/// <summary>
|
|
/// Overloaded Constructor
|
|
/// </summary>
|
|
/// <param name="projectFilePath">path to the project file</param>
|
|
/// <param name="serviceProvider">A service provider.</param>
|
|
public ProjectSecurityChecker(IServiceProvider serviceProvider, string projectFilePath)
|
|
{
|
|
if(serviceProvider == null)
|
|
{
|
|
throw new ArgumentNullException("serviceProvider");
|
|
}
|
|
|
|
if(String.IsNullOrEmpty(projectFilePath))
|
|
{
|
|
throw new ArgumentException(SR.GetString(SR.ParameterCannotBeNullOrEmpty, CultureInfo.CurrentUICulture), "projectFilePath");
|
|
}
|
|
|
|
this.serviceProvider = serviceProvider;
|
|
|
|
// Instantiate a new project shim that we are going to use for security checkings.
|
|
EngineShim engine = new EngineShim();
|
|
this.projectShim = engine.CreateNewProject();
|
|
this.projectShim.Load(projectFilePath);
|
|
}
|
|
#endregion
|
|
|
|
#region IDisposable Members
|
|
|
|
/// <summary>
|
|
/// The IDispose interface Dispose method for disposing the object determinastically.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
this.Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region virtual methods
|
|
/// <summary>
|
|
/// Check if the project is safe at load/design time
|
|
/// </summary>
|
|
/// <param name="securityErrorMessage">If the project is not safe contains an error message, describing the reason.</param>
|
|
/// <returns>true if the project is safe, false otherwise</returns>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
|
|
Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
|
|
public virtual bool IsProjectSafeAtLoadTime(out string securityErrorMessage)
|
|
{
|
|
securityErrorMessage = String.Empty;
|
|
|
|
StringBuilder securityMessageMaker = new StringBuilder();
|
|
int counter = 0;
|
|
string tempMessage;
|
|
|
|
// STEP 1: Check direct imports.
|
|
if(!this.IsProjectSafeWithImports(out tempMessage))
|
|
{
|
|
ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
|
|
securityErrorMessage = tempMessage;
|
|
}
|
|
|
|
// STEP 2: Check dangerous properties
|
|
if(!this.IsProjectSafeWithProperties(out tempMessage))
|
|
{
|
|
ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
|
|
securityErrorMessage = tempMessage;
|
|
}
|
|
|
|
// STEP 3: Check dangerous targets
|
|
if(!this.IsProjectSafeWithTargets(out tempMessage))
|
|
{
|
|
ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
|
|
securityErrorMessage = tempMessage;
|
|
}
|
|
|
|
// STEP 4: Check dangerous items
|
|
if(!this.IsProjectSafeWithItems(out tempMessage))
|
|
{
|
|
ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
|
|
securityErrorMessage = tempMessage;
|
|
}
|
|
|
|
// STEP 5: Check UsingTask tasks
|
|
if(!this.IsProjectSafeWithUsingTasks(out tempMessage))
|
|
{
|
|
ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
|
|
securityErrorMessage = tempMessage;
|
|
}
|
|
|
|
// STEP 6: Check for items defined within the LoadTimeCheckItemLocation, whether they are defined in safe locations
|
|
if(!this.CheckItemsLocation(out tempMessage))
|
|
{
|
|
securityMessageMaker.AppendFormat(CultureInfo.CurrentCulture, "{0}: ", (++counter).ToString(CultureInfo.CurrentCulture));
|
|
securityMessageMaker.AppendLine(tempMessage);
|
|
securityErrorMessage = tempMessage;
|
|
}
|
|
|
|
if(counter > 1)
|
|
{
|
|
securityErrorMessage = securityMessageMaker.ToString();
|
|
}
|
|
|
|
return String.IsNullOrEmpty(securityErrorMessage);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the project is safe with imports. The project file is considered
|
|
/// unsafe if it contains any imports not registered in the safe import regkey.
|
|
/// </summary>
|
|
/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
|
|
/// <returns>true if the project is safe regarding imports.</returns>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
|
|
Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
|
|
protected virtual bool IsProjectSafeWithImports(out string securityErrorMessage)
|
|
{
|
|
securityErrorMessage = String.Empty;
|
|
|
|
// Now get the directly imports and do the comparision.
|
|
string[] directImports = this.securityCheckHelper.GetDirectlyImportedProjects(this.projectShim);
|
|
if(directImports != null && directImports.Length > 0)
|
|
{
|
|
IList<string> safeImportList = ProjectSecurityChecker.GetSafeImportList();
|
|
|
|
for(int i = 0; i < directImports.Length; i++)
|
|
{
|
|
string fileToCheck = directImports[i];
|
|
if(!ProjectSecurityChecker.IsSafeImport(safeImportList, fileToCheck))
|
|
{
|
|
using(RegistryKey root = VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration))
|
|
{
|
|
securityErrorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.DetailsImport, CultureInfo.CurrentUICulture), Path.GetFileName(this.projectShim.FullFileName), fileToCheck, Path.Combine(root.Name, SafeImportsSubkey));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Checks if the project is safe regarding properties.
|
|
/// </summary>
|
|
/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
|
|
/// <returns>true if the project has only safe properties.</returns>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
|
|
Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
|
|
protected virtual bool IsProjectSafeWithProperties(out string securityErrorMessage)
|
|
{
|
|
securityErrorMessage = String.Empty;
|
|
|
|
// Now ask the security check heper for the safe properties.
|
|
string reasonForFailure;
|
|
bool isUserFile;
|
|
bool isProjectSafe = this.securityCheckHelper.IsProjectSafe(ProjectSecurityChecker.DangerousPropertyProperty,
|
|
ProjectSecurityChecker.DefaultDangerousProperties,
|
|
this.projectShim,
|
|
null,
|
|
SecurityCheckPass.Properties,
|
|
out reasonForFailure,
|
|
out isUserFile);
|
|
|
|
if(!isProjectSafe)
|
|
{
|
|
securityErrorMessage = this.GetMessageString(reasonForFailure, SR.DetailsProperty);
|
|
}
|
|
|
|
return isProjectSafe;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the project is safe regarding targets.
|
|
/// </summary>
|
|
/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
|
|
/// <returns>true if the project has only safe targets.</returns>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
|
|
Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
|
|
protected virtual bool IsProjectSafeWithTargets(out string securityErrorMessage)
|
|
{
|
|
securityErrorMessage = String.Empty;
|
|
|
|
// Now ask the security check heper for the safe targets.
|
|
string reasonForFailure;
|
|
bool isUserFile;
|
|
bool isProjectSafe = this.securityCheckHelper.IsProjectSafe(ProjectSecurityChecker.DangerousTargetProperty,
|
|
ProjectSecurityChecker.DefaultDangerousTargets,
|
|
this.projectShim,
|
|
null,
|
|
SecurityCheckPass.Targets,
|
|
out reasonForFailure,
|
|
out isUserFile);
|
|
|
|
if(!isProjectSafe)
|
|
{
|
|
securityErrorMessage = this.GetMessageString(reasonForFailure, SR.DetailsTarget);
|
|
}
|
|
|
|
return isProjectSafe;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the project is safe regarding items.
|
|
/// </summary>
|
|
/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
|
|
/// <returns>true if the project has only safe items.</returns>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
|
|
Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
|
|
protected virtual bool IsProjectSafeWithItems(out string securityErrorMessage)
|
|
{
|
|
securityErrorMessage = String.Empty;
|
|
|
|
// Now ask the security check heper for the safe items.
|
|
string reasonForFailure;
|
|
bool isUserFile;
|
|
bool isProjectSafe = this.securityCheckHelper.IsProjectSafe(ProjectSecurityChecker.DangerousItemsProperty,
|
|
ProjectSecurityChecker.DefaultDangerousItems,
|
|
this.projectShim,
|
|
null,
|
|
SecurityCheckPass.Items,
|
|
out reasonForFailure,
|
|
out isUserFile);
|
|
|
|
if(!isProjectSafe)
|
|
{
|
|
securityErrorMessage = this.GetMessageString(reasonForFailure, SR.DetailsItem);
|
|
}
|
|
|
|
return isProjectSafe;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the project is safe with using tasks.
|
|
/// </summary>
|
|
/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
|
|
/// <returns>true if the project has no using tasks defined in the project file.</returns>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
|
|
Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
|
|
protected virtual bool IsProjectSafeWithUsingTasks(out string securityErrorMessage)
|
|
{
|
|
securityErrorMessage = String.Empty;
|
|
|
|
string[] usingTasks = this.securityCheckHelper.GetNonImportedUsingTasks(this.projectShim);
|
|
|
|
if(usingTasks != null && usingTasks.Length > 0)
|
|
{
|
|
securityErrorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.DetailsUsingTask, CultureInfo.CurrentUICulture), Path.GetFileName(this.projectShim.FullFileName), usingTasks[0]);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// If the project contains the LoadTimeCheckItemsWithinProjectCone property, the method verifies that all the items listed in there are within the project cone.
|
|
/// Also checks that the project is not in Program Files or Windows if the property was there.
|
|
/// </summary>
|
|
/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
|
|
/// <returns>true if the project has no badly defined project items.</returns>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
|
|
Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
|
|
protected virtual bool CheckItemsLocation(out string securityErrorMessage)
|
|
{
|
|
securityErrorMessage = String.Empty;
|
|
|
|
// Get the <LoadTimeCheckItemLocation> property from the project
|
|
string itemLocationProperty = this.projectShim.GetEvaluatedProperty(ProjectSecurityChecker.CheckItemLocationProperty);
|
|
|
|
if(String.IsNullOrEmpty(itemLocationProperty))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Takes a semicolon separated list of entries, splits them and puts them into a list with values trimmed.
|
|
string[] items = itemLocationProperty.Split(ProjectSecurityChecker.DangerousListSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
IList<string> itemsToCheck = new List<string>();
|
|
foreach(string item in items)
|
|
{
|
|
itemsToCheck.Add(item.Trim());
|
|
}
|
|
|
|
// Now check the items for being defined in a safe location.
|
|
string reasonForFailure;
|
|
ItemSecurityChecker itemsSecurityChecker = new ItemSecurityChecker(this.serviceProvider, this.projectShim.FullFileName);
|
|
if(!itemsSecurityChecker.CheckItemsSecurity(this.projectShim, itemsToCheck, out reasonForFailure))
|
|
{
|
|
securityErrorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.DetailsItemLocation, CultureInfo.CurrentUICulture), Path.GetFileName(this.projectShim.FullFileName), reasonForFailure);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// The method that does the cleanup.
|
|
/// </summary>
|
|
/// <param name="disposing">true if called from IDispose.Dispose; false if called from Finalizer.</param>
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
// Everybody can go here.
|
|
if(!this.isDisposed)
|
|
{
|
|
// Synchronize calls to the Dispose simultaniously.
|
|
lock(Mutex)
|
|
{
|
|
if(disposing)
|
|
{
|
|
this.projectShim.ParentEngine.UnloadProject(this.projectShim);
|
|
}
|
|
|
|
this.isDisposed = true;
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region helper methods
|
|
/// <summary>
|
|
/// Gets a message string that has an associated format with a reason for failure.
|
|
/// </summary>
|
|
/// <param name="reasonForFailure"></param>
|
|
/// <param name="resourceID"></param>
|
|
/// <returns></returns>
|
|
internal string GetMessageString(string reasonForFailure, string resourceID)
|
|
{
|
|
Debug.Assert(!String.IsNullOrEmpty(reasonForFailure), "The reason for failure should not be empty or null");
|
|
Debug.Assert(!String.IsNullOrEmpty(resourceID), "The resource id string cannot be empty");
|
|
|
|
return String.Format(CultureInfo.CurrentCulture, SR.GetString(resourceID, Path.GetFileName(this.projectShim.FullFileName), reasonForFailure));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a format string that will be pushed to the More Detailed dialog.
|
|
/// </summary>
|
|
/// <param name="securityMessageMaker">The Stringbuilder object containing the formatted message.</param>
|
|
/// <param name="counter">The 'issue' number.</param>
|
|
/// <param name="securityErrorMessage">The message to format.</param>
|
|
private static void FormatMessage(StringBuilder securityMessageMaker, int counter, string securityErrorMessage)
|
|
{
|
|
securityMessageMaker.AppendFormat(CultureInfo.CurrentCulture, "{0}: ", counter.ToString(CultureInfo.CurrentCulture));
|
|
securityMessageMaker.AppendLine(securityErrorMessage);
|
|
securityMessageMaker.Append(Environment.NewLine);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a set of file info's describing the files in the SafeImports registry location.
|
|
/// </summary>
|
|
/// <returns>A set of FileInfo objects describing the files in the SafeImports location.</returns>
|
|
private static IList<string> GetSafeImportList()
|
|
{
|
|
List<string> importsList = new List<string>();
|
|
|
|
using(RegistryKey root = VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration))
|
|
{
|
|
if(root != null)
|
|
{
|
|
using(RegistryKey key = root.OpenSubKey(SafeImportsSubkey))
|
|
{
|
|
if(key != null)
|
|
{
|
|
foreach(string value in key.GetValueNames())
|
|
{
|
|
string keyValue = key.GetValue(value, String.Empty, RegistryValueOptions.None) as string;
|
|
// Make sure that the environment variables are expanded.
|
|
keyValue = System.Environment.ExpandEnvironmentVariables(keyValue);
|
|
Uri uri;
|
|
if(!String.IsNullOrEmpty(keyValue) && Uri.TryCreate(keyValue, UriKind.Absolute, out uri) && uri.IsAbsoluteUri)
|
|
{
|
|
importsList.Add(keyValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return importsList;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if an import is a safe import.
|
|
/// </summary>
|
|
/// <param name="safeImportsList">A list of safe imports from teh registry.</param>
|
|
/// <param name="fileToCheck">The file to check.</param>
|
|
/// <returns>true if the file to check can be found in the safe import list</returns>
|
|
private static bool IsSafeImport(IList<string> safeImportsList, string fileToCheck)
|
|
{
|
|
foreach(string safeImport in safeImportsList)
|
|
{
|
|
if(NativeMethods.IsSamePath(safeImport, fileToCheck))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endregion
|
|
|
|
#region nested types
|
|
/// <summary>
|
|
/// Class for checking that the items defined in LoadTimeCheckItemLocation are being defined in safe locations.
|
|
/// </summary>
|
|
private class ItemSecurityChecker
|
|
{
|
|
#region fields
|
|
/// <summary>
|
|
/// The associated service provider.
|
|
/// </summary>
|
|
private IServiceProvider serviceProvider;
|
|
|
|
/// <summary>
|
|
/// The solutionFolder;
|
|
/// </summary>
|
|
private Uri solutionFolder;
|
|
|
|
/// <summary>
|
|
/// The project folder
|
|
/// </summary>
|
|
private Uri projectFolder;
|
|
|
|
/// <summary>
|
|
/// The set of special folders.
|
|
/// </summary>
|
|
private IList<Uri> specialFolders;
|
|
#endregion
|
|
|
|
#region ctors
|
|
/// <summary>
|
|
/// Overloaded Constructor
|
|
/// </summary>
|
|
/// <param name="projectFilePath">path to the project file</param>
|
|
/// <param name="serviceProvider">A service provider.</param>
|
|
internal ItemSecurityChecker(IServiceProvider serviceProvider, string projectFullPath)
|
|
{
|
|
this.serviceProvider = serviceProvider;
|
|
|
|
// Initialize the project and solution folders.
|
|
this.SetProjectFolder(projectFullPath);
|
|
this.SetSolutionFolder();
|
|
|
|
// Set the special folders. Maybe this should be a static.
|
|
this.specialFolders = ItemSecurityChecker.SetSpecialFolders();
|
|
}
|
|
#endregion
|
|
|
|
#region methods
|
|
/// <summary>
|
|
/// Checks whether a set of project items described by the LoadTimeCheckItemLocation are in a safe location.
|
|
/// </summary>
|
|
/// <param name="projectShim">The project shim containing the items to be checked.</param>
|
|
/// <param name="itemsToCheck">The list of items to check if they are in the project cone.</param>
|
|
/// <param name="reasonForFailure">The reason for failure if any of the files fails</param>
|
|
/// <returns>true if all project items are in the project cone. Otherwise false.</returns>
|
|
internal bool CheckItemsSecurity(ProjectShim projectShim, IList<string> itemsToCheck, out string reasonForFailure)
|
|
{
|
|
reasonForFailure = String.Empty;
|
|
|
|
// If nothing to check assume that everything is ok.
|
|
if(itemsToCheck == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
Debug.Assert(projectShim != null, "Cannot check the items if no project has been defined!");
|
|
|
|
foreach(string itemName in itemsToCheck)
|
|
{
|
|
BuildItemGroupShim group = projectShim.GetEvaluatedItemsByNameIgnoringCondition(itemName);
|
|
if(group != null)
|
|
{
|
|
IEnumerator enumerator = group.GetEnumerator();
|
|
while(enumerator.MoveNext())
|
|
{
|
|
BuildItemShim item = enumerator.Current as BuildItemShim;
|
|
|
|
string finalItem = item.FinalItemSpec;
|
|
|
|
if(!String.IsNullOrEmpty(finalItem))
|
|
{
|
|
// Perform the actual check - start with normalizing the path. Relative paths
|
|
// should be treated as relative to the project file.
|
|
string fullPath = this.GetFullPath(finalItem);
|
|
|
|
// If the fullpath of the item is suspiciously short do not check it.
|
|
if(fullPath.Length >= 3)
|
|
{
|
|
Uri uri = null;
|
|
|
|
// If we cannot create a uri from the item path return with the error
|
|
if(!Uri.TryCreate(fullPath, UriKind.Absolute, out uri))
|
|
{
|
|
reasonForFailure = fullPath;
|
|
return false;
|
|
}
|
|
|
|
// Check if the item points to a network share
|
|
if(uri.IsUnc)
|
|
{
|
|
reasonForFailure = fullPath;
|
|
return false;
|
|
}
|
|
|
|
// Check if the item is located in a drive root directory
|
|
if(uri.Segments.Length == 3 && uri.Segments[1] == ":" && uri.Segments[2][0] == Path.DirectorySeparatorChar)
|
|
{
|
|
reasonForFailure = fullPath;
|
|
return false;
|
|
}
|
|
|
|
//Check if the item is not in a special folder.
|
|
foreach(Uri specialFolder in this.specialFolders)
|
|
{
|
|
if(ItemSecurityChecker.IsItemInCone(uri, specialFolder))
|
|
{
|
|
reasonForFailure = fullPath;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
reasonForFailure = fullPath;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the list of special directories. This method should be optimized if called more then once.
|
|
/// </summary>
|
|
/// <returns>The list of special directories</returns>
|
|
private static IList<Uri> SetSpecialFolders()
|
|
{
|
|
string[] specialFolderArray = new string[5]
|
|
{
|
|
Environment.GetFolderPath(Environment.SpecialFolder.System),
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
|
|
Environment.GetFolderPath(Environment.SpecialFolder.Startup),
|
|
ItemSecurityChecker.GetSpecialDirectoryFromNative(NativeMethods.ExtendedSpecialFolder.Windows),
|
|
ItemSecurityChecker.GetSpecialDirectoryFromNative(NativeMethods.ExtendedSpecialFolder.CommonStartup)
|
|
};
|
|
|
|
List<Uri> specialFolders = new List<Uri>(5);
|
|
|
|
// Add trailing backslash to the folders.
|
|
foreach(string specialFolder in specialFolderArray)
|
|
{
|
|
string tempFolder = specialFolder;
|
|
if(!tempFolder.EndsWith("\\", StringComparison.Ordinal))
|
|
{
|
|
tempFolder += "\\";
|
|
}
|
|
|
|
specialFolders.Add(new Uri(tempFolder));
|
|
}
|
|
|
|
return specialFolders;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Some special folders are not supported by System.Environment.GetFolderPath. Get these special folders using p/invoke.
|
|
/// </summary>
|
|
/// <param name="specialFolder">The type of special folder to retrieve.</param>
|
|
/// <returns>The folder path</returns>
|
|
private static string GetSpecialDirectoryFromNative(NativeMethods.ExtendedSpecialFolder extendedSpecialFolder)
|
|
{
|
|
string specialFolder = null;
|
|
IntPtr buffer = IntPtr.Zero;
|
|
|
|
// Demand Unmanaged code permission. It should be normal to demand UnmanagedCodePermission from an assembly integrating into VS.
|
|
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
|
|
try
|
|
{
|
|
buffer = Marshal.AllocHGlobal((NativeMethods.MAX_PATH + 1) * 2);
|
|
IntPtr[] pathIdentifier = new IntPtr[1];
|
|
|
|
if(ErrorHandler.Succeeded(UnsafeNativeMethods.SHGetSpecialFolderLocation(IntPtr.Zero, (int)extendedSpecialFolder, pathIdentifier)) && UnsafeNativeMethods.SHGetPathFromIDList(pathIdentifier[0], buffer))
|
|
{
|
|
specialFolder = Marshal.PtrToStringAuto(buffer);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if(buffer != IntPtr.Zero)
|
|
{
|
|
Marshal.FreeHGlobal(buffer);
|
|
}
|
|
}
|
|
|
|
|
|
return specialFolder;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Checks if the itemToCheck is in the cone of the baseUri.
|
|
/// </summary>
|
|
/// <param name="itemToCheck">The item to check</param>
|
|
/// <param name="baseUri">The base to the item. This should define a folder.</param>
|
|
/// <returns>true if the item to check is in the cone of the baseUri.</returns>
|
|
private static bool IsItemInCone(Uri itemToCheck, Uri baseUri)
|
|
{
|
|
Debug.Assert(itemToCheck != null && baseUri != null, "Cannot check for items since the input is wrong");
|
|
Debug.Assert(!NativeMethods.IsSamePath(Path.GetDirectoryName(baseUri.LocalPath), baseUri.LocalPath), "The " + baseUri.LocalPath + " is not a folder!");
|
|
|
|
return (itemToCheck.IsFile && baseUri.IsFile &&
|
|
String.Compare(itemToCheck.LocalPath, 0, baseUri.LocalPath, 0, baseUri.LocalPath.Length, StringComparison.OrdinalIgnoreCase) == 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the solution folder.
|
|
/// </summary>
|
|
private void SetSolutionFolder()
|
|
{
|
|
if(this.solutionFolder != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
IVsSolution solution = this.serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution;
|
|
Debug.Assert(solution != null, "Could not retrieve the solution service from the global service provider");
|
|
|
|
string solutionDirectory, solutionFile, userOptionsFile;
|
|
|
|
// We do not want to throw. If we cannot set the solution related constants we set them to empty string.
|
|
ErrorHandler.ThrowOnFailure(solution.GetSolutionInfo(out solutionDirectory, out solutionFile, out userOptionsFile));
|
|
|
|
if(String.IsNullOrEmpty(solutionDirectory))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make sure the solution dir ends with a backslash
|
|
if(solutionDirectory[solutionDirectory.Length - 1] != Path.DirectorySeparatorChar)
|
|
{
|
|
solutionDirectory += Path.DirectorySeparatorChar;
|
|
}
|
|
|
|
Uri.TryCreate(solutionDirectory, UriKind.Absolute, out this.solutionFolder);
|
|
|
|
Debug.Assert(this.solutionFolder != null, "Could not create the Uri for the solution folder");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the project folder.
|
|
/// </summary>
|
|
/// <param name="projectFullPath">The path to the project</param>
|
|
private void SetProjectFolder(string projectFullPath)
|
|
{
|
|
if(this.projectFolder != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string tempProjectFolder = Path.GetDirectoryName(projectFullPath);
|
|
|
|
// Make sure the project dir ends with a backslash
|
|
if(!tempProjectFolder.EndsWith("\\", StringComparison.Ordinal) && !tempProjectFolder.EndsWith("/", StringComparison.Ordinal))
|
|
{
|
|
tempProjectFolder += "\\";
|
|
}
|
|
|
|
Uri.TryCreate(tempProjectFolder, UriKind.Absolute, out this.projectFolder);
|
|
|
|
Debug.Assert(this.projectFolder != null, "Could not create the Uri for the project folder");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the fullpath of an item.
|
|
/// Relative pathes are treated as relative to the project file.
|
|
/// </summary>
|
|
/// <param name="item">The item.</param>
|
|
/// <returns>The ful path of the item.</returns>
|
|
private string GetFullPath(string item)
|
|
{
|
|
Url url;
|
|
if(Path.IsPathRooted(item))
|
|
{
|
|
// Use absolute path
|
|
url = new Microsoft.VisualStudio.Shell.Url(item);
|
|
}
|
|
else
|
|
{
|
|
// Path is relative, so make it relative to project path
|
|
url = new Url(new Url(this.projectFolder.LocalPath), item);
|
|
}
|
|
|
|
return url.AbsoluteUrl;
|
|
}
|
|
#endregion
|
|
}
|
|
#endregion
|
|
}
|
|
}
|