Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new analyzer for ICorsHeaderAppender/CorsHeaderAppender usage #326

Merged
merged 3 commits into from
Apr 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/D2L.CodeStyle.Analyzers/D2L.CodeStyle.Analyzers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="Visibility\CorsHeaderAppenderUsageAnalyzer.cs" />
<Compile Include="Immutability\ImmutableGenericAttributeAnalyzer.cs" />
<Compile Include="ApiUsage\JsonParamBinderAttribute\JsonParamBinderAnalyzer.cs" />
<Compile Include="ApiUsage\JsonParamBinderAttribute\JsonParamBinderAnalyzerFixer.cs" />
Expand Down
10 changes: 10 additions & 0 deletions src/D2L.CodeStyle.Analyzers/Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,5 +271,15 @@ public static class Diagnostics {
isEnabledByDefault: true,
description: "Unnecessary mutability annotations should be removed to keep the code base clean"
);

public static readonly DiagnosticDescriptor DangerousUsageOfCorsHeaderAppender = new DiagnosticDescriptor(
id: "D2L0031",
title: "ICorsHeaderAppender should not be used, as it can introduce security vulnerabilities.",
messageFormat: "ICorsHeaderAppender should not be used, as it can introduce security vulnerabilities.",
category: "Safety",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "ICorsHeaderAppender should not be used, as it can introduce security vulnerabilities."
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using D2L.CodeStyle.Analyzers.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace D2L.CodeStyle.Analyzers.Visibility {

[DiagnosticAnalyzer( LanguageNames.CSharp )]
internal sealed class CorsHeaderAppenderUsageAnalyzer : DiagnosticAnalyzer {

private const string CorsInterface = "D2L.LP.Web.Cors.ICorsHeaderAppender";
private const string CorsClass = "D2L.LP.Web.Cors.CorsHeaderAppender";

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
=> ImmutableArray.Create(
Diagnostics.DangerousUsageOfCorsHeaderAppender
);

public override void Initialize( AnalysisContext context ) {
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(
RegisterCorsHeadersUsageAnalyzer
);
}

public void RegisterCorsHeadersUsageAnalyzer(
CompilationStartAnalysisContext context
) {
INamedTypeSymbol interfaceType = context.Compilation
.GetTypeByMetadataName( CorsInterface );

if( !interfaceType.IsNullOrErrorType() ) {
context.RegisterSyntaxNodeAction(
ctx => PreventInjection(
ctx,
interfaceType
),
SyntaxKind.ConstructorDeclaration
);
}

INamedTypeSymbol classType = context.Compilation
.GetTypeByMetadataName( CorsClass );

if( !classType.IsNullOrErrorType() ) {
context.RegisterSyntaxNodeAction(
ctx => PreventManualInstantiation(
ctx,
classType
),
SyntaxKind.ObjectCreationExpression
);
}

}

/// <summary>
/// Prevent injection of the ICorsHttpHeaderHelper interface except in
/// a specific whitelist of classes.
/// </summary>
private static void PreventInjection(
SyntaxNodeAnalysisContext context,
INamedTypeSymbol interfaceType
) {
var constructor = context.Node as ConstructorDeclarationSyntax;
if( constructor == null ) {
return;
}

foreach( var parameter in constructor.ParameterList.Parameters ) {
INamedTypeSymbol paramType = context.SemanticModel
.GetTypeInfo( parameter.Type )
.Type as INamedTypeSymbol;

if( paramType == null || !paramType.Equals( interfaceType ) ) {
return;
}

var parentClasses = context.Node
.Ancestors()
.Where( a => a.IsKind( SyntaxKind.ClassDeclaration ) );

var parentSymbols = parentClasses.Select(
c => context.SemanticModel.GetDeclaredSymbol( c )
);

if( parentSymbols.Any( IsClassWhitelisted ) ) {
return;
}

context.ReportDiagnostic(
Diagnostic.Create(
Diagnostics.DangerousUsageOfCorsHeaderAppender,
parameter.GetLocation()
)
);
}
}

/// <summary>
/// We never want to allow manual instantiation of this object, so no
/// whitelist here.
/// </summary>
private static void PreventManualInstantiation(
SyntaxNodeAnalysisContext context,
INamedTypeSymbol classType
) {
var instantiation = context.Node as ObjectCreationExpressionSyntax;
if( instantiation == null ) {
return;
}

INamedTypeSymbol baseType = context.SemanticModel
.GetTypeInfo( instantiation )
.Type as INamedTypeSymbol;

if( baseType == null || !baseType.Equals( classType ) ) {
return;
}

context.ReportDiagnostic(
Diagnostic.Create(
Diagnostics.DangerousUsageOfCorsHeaderAppender,
context.Node.GetLocation()
)
);
}

/// <summary>
/// A list of classes that have been cleared to implement these headers
/// </summary>
private static readonly ImmutableHashSet<string> WhitelistedClasses = new HashSet<string> {
"D2L.LP.Web.ContentHandling.Handlers.ContentHttpHandler",
"D2L.LP.Web.Files.FileViewing.StreamFileViewerResult",
"D2L.LP.Web.Files.FileViewing.Default.StreamFileViewerResultFactory"
}.ToImmutableHashSet();

private static bool IsClassWhitelisted( ISymbol classSymbol ) {
return WhitelistedClasses.Contains( classSymbol.ToString() );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<EmbeddedResource Include="Specs\ImmutabilityExceptionInheritanceAnalyzer.cs" />
<EmbeddedResource Include="Specs\ImmutableCollectionsAnalyzer.cs" />
<EmbeddedResource Include="Specs\ImmutableGenericAttributeAnalyzer.cs" />
<EmbeddedResource Include="Specs\CorsHeaderAppenderUsageAnalyzer.cs" />
<Compile Include="TestBase.cs" />
<Compile Include="Extensions\TypeSymbolExtensionsTests.cs" />
<Compile Include="ApiUsage\NotNullAnalyzerTests.cs" />
Expand Down Expand Up @@ -167,4 +168,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// analyzer: D2L.CodeStyle.Analyzers.Visibility.CorsHeaderAppenderUsageAnalyzer

using D2L.LP.Web.Cors;

namespace D2L.LP.Web.Cors {

public interface ICorsHeaderAppender { }

public class CorsHeaderAppender : ICorsHeaderAppender {

public CorsHeaderAppender() { }

public CorsHeaderAppender( int i ) { }

}

public interface ISomeOtherSafeInterface { }

public class SomeOtherSafeClass : ISomeOtherSafeInterface { }

}

namespace D2L.LP.Web.ContentHandling.Handlers {

public class ContentHttpHandler { // Whitelisted by necessity

public ContentHttpHandler(
ICorsHeaderAppender corsHelper
) { }

}

}

namespace D2L.LP.Web.Files.FileViewing { // Whitelisted by necessity

public class StreamFileViewerResult {

public StreamFileViewerResult(
ICorsHeaderAppender corsHelper
) { }

}

}

namespace D2L.LP.Web.Files.FileViewing.Default {

public class StreamFileViewerResultFactory { // Whitelisted by necessity

public StreamFileViewerResultFactory(
ICorsHeaderAppender corsHelper
) { }

}

}

namespace D2L.CodeStyle.Analyzers.CorsHeaderAppenderUsageAnalyzer.Examples {

public sealed class BadClass {

public BadClass(
/* DangerousUsageOfCorsHeaderAppender */ ICorsHeaderAppender corsHelper /**/
) { }

public void UsesCorsHelper_ManualInstantiation_NoParams() {
ICorsHeaderAppender corsHelper = /* DangerousUsageOfCorsHeaderAppender */ new CorsHeaderAppender() /**/;
}

public void UsesCorsHelper_ManualInstantiation_WithParams() {
const int i = 10;
ICorsHeaderAppender corsHelper = /* DangerousUsageOfCorsHeaderAppender */ new CorsHeaderAppender( i ) /**/;
}

public void UsesCorsHelper_ManualInstantiation_OnlyInstantiationTriggersDiagnostic() {
CorsHeaderAppender corsHelper = /* DangerousUsageOfCorsHeaderAppender */ new CorsHeaderAppender() /**/;
}
}

public sealed class GoodClass {

ISomeOtherSafeInterface m_safeInterface;

public GoodClass(
ISomeOtherSafeInterface safeInterface
) {
m_safeInterface = safeInterface;
}

public void SafeMethod() {
ISomeOtherSafeInterface safeInterface = new SomeOtherSafeClass();
}

}

}