Skip to content

Commit

Permalink
[IAST] Support for specifying aspect min version (#5931) [-> V2] (#5932)
Browse files Browse the repository at this point in the history
## Summary of changes
Added a mechanism by which an aspect or aspect class can define a
minimum native version in order to be applied.

## Reason for change
Some aspects defined under new features (like aspects for struct
methods, or aspects with generics) can break old native implementations,
so, we need a way to avoid those to be sent or applied.

## Implementation details
Created `FromVersionAttribute` versions of all Aspect attributes. These
inherit from the original attribute adding a **minimum version of the
native library** from which the aspect will be applied. This version is
then added in the end of the aspect line, in a way that makes old native
tracers ignore those lines, as it breaks the old specification. For new
tracers who are able to read that version info, a version comparison
will determine if that line is accepted or dumped, allowing us to fine
tune the aspects set applied.

This aspect attribute which states that this aspect will be only applied
if a native tracer with version `3.2.0` or bigger is found
`[AspectMethodReplaceFromVersion("3.2.0",
"System.String::Concat(System.Collections.Generic.IEnumerable)")]` will
generate this aspect line


`[AspectMethodReplace(\"System.String::Concat(System.Collections.Generic.IEnumerable)\",\"\",[0],[False],[None],Default,[]);V3.2.0]
Concat(System.Collections.Generic.IEnumerable)`

Notice that the version goes in the end, and that's because old tracers
while parsing the line were looking for the text `)] ` This will make
old unprepared tracers to automatically ditch the line, whereas the new
one implemented from this PR, will be able to retrieve, parse and decide
what to do with that version info.

## Test coverage
Source generator tests have been written to ensure the correct use and
aspect generation of version flavor aspect attributes.

## Other details


<!-- ⚠️ Note: where possible, please obtain 2 approvals prior to
merging. Unless CODEOWNERS specifies otherwise, for external teams it is
typically best to have one review from a team member, and one review
from apm-dotnet. Trivial changes do not require 2 reviews. -->

## Summary of changes

## Reason for change

## Implementation details

## Test coverage

## Other details
<!-- Fixes #{issue} -->

<!-- ⚠️ Note: where possible, please obtain 2 approvals prior to
merging. Unless CODEOWNERS specifies otherwise, for external teams it is
typically best to have one review from a team member, and one review
from apm-dotnet. Trivial changes do not require 2 reviews. -->
  • Loading branch information
daniel-romano-DD authored Aug 23, 2024
1 parent 9f69b4b commit 2bd1621
Show file tree
Hide file tree
Showing 19 changed files with 293 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -182,65 +182,75 @@ private static string GetAspectLine(AttributeData data)
{
if (data is null || data.AttributeClass is null) { return string.Empty; }

var arguments = data.ConstructorArguments.Select(GetArgument).ToArray();
var arguments = data.ConstructorArguments.Select(GetArgument).ToList();
var name = data.AttributeClass.Name;
var version = string.Empty;

return data.AttributeClass.Name switch
if (name.EndsWith("FromVersionAttribute"))
{
// Aspect with version limitation
name = name.Replace("FromVersionAttribute", "Attribute");
version = ";V" + arguments[0].Trim('"');
arguments.RemoveAt(0);
}

return name switch
{
// Coments are to have the original attributes overloads present
// AspectClassAttribute(string defaultAssembly, AspectFilter[] filters, AspectType defaultAspectType = AspectType.Propagation, VulnerabilityType[] defaultVulnerabilityTypes)
"AspectClassAttribute" => arguments.Length switch
"AspectClassAttribute" => arguments.Count switch
{
// AspectClassAttribute(string defaultAssembly)
1 => $"[AspectClass({arguments[0]},[None],Propagation,[])]",
1 => $"[AspectClass({arguments[0]},[None],Propagation,[]){version}]",
// AspectClassAttribute(string defaultAssembly, AspectType defaultAspectType, params VulnerabilityType[] defaultVulnerabilityTypes)
3 => $"[AspectClass({arguments[0]},[None],{arguments[1]},{Check(arguments[2])})]",
3 => $"[AspectClass({arguments[0]},[None],{arguments[1]},{Check(arguments[2])}){version}]",
// AspectClassAttribute(string defaultAssembly, AspectFilter[] filters, AspectType defaultAspectType = AspectType.Propagation, params VulnerabilityType[] defaultVulnerabilityTypes)
4 => $"[AspectClass({arguments[0]},{arguments[1]},{arguments[2]},{Check(arguments[3])})]",
_ => throw new ArgumentException($"Could not find AspectClassAttribute overload with {arguments.Length} parameters")
4 => $"[AspectClass({arguments[0]},{arguments[1]},{arguments[2]},{Check(arguments[3])}){version}]",
_ => throw new ArgumentException($"Could not find AspectClassAttribute overload with {arguments.Count} parameters")
},
// AspectAttribute(string targetMethod, string targetType, int[] paramShift, bool[] boxParam, AspectFilter[] filters, AspectType aspectType = AspectType.Propagation, VulnerabilityType[] vulnerabilityTypes)
"AspectCtorReplaceAttribute" => arguments.Length switch
"AspectCtorReplaceAttribute" => arguments.Count switch
{
// AspectCtorReplaceAttribute(string targetMethod)
1 => $"[AspectCtorReplace({arguments[0]},\"\",[0],[False],[None],Default,[])]",
1 => $"[AspectCtorReplace({arguments[0]},\"\",[0],[False],[None],Default,[]){version}]",
// AspectCtorReplaceAttribute(string targetMethod, params AspectFilter[] filters)
2 => $"[AspectCtorReplace({arguments[0]},\"\",[0],[False],{Check(arguments[1])},Default,[])]",
2 => $"[AspectCtorReplace({arguments[0]},\"\",[0],[False],{Check(arguments[1])},Default,[]){version}]",
// AspectCtorReplaceAttribute(string targetMethod, AspectType aspectType = AspectType.Default, params VulnerabilityType[] vulnerabilityTypes)
3 => $"[AspectCtorReplace({arguments[0]},\"\",[0],[False],[None],{arguments[1]},{Check(arguments[2])})]",
3 => $"[AspectCtorReplace({arguments[0]},\"\",[0],[False],[None],{arguments[1]},{Check(arguments[2])}){version}]",
// AspectCtorReplaceAttribute(string targetMethod, AspectFilter[] filters, AspectType aspectType = AspectType.Default, params VulnerabilityType[] vulnerabilityTypes)
4 => $"[AspectCtorReplace({arguments[0]},\"\",[0],[False],[{arguments[1]}],{arguments[2]},{Check(arguments[3])})]",
_ => throw new ArgumentException($"Could not find AspectCtorReplaceAttribute overload with {arguments.Length} parameters")
4 => $"[AspectCtorReplace({arguments[0]},\"\",[0],[False],[{arguments[1]}],{arguments[2]},{Check(arguments[3])}){version}]",
_ => throw new ArgumentException($"Could not find AspectCtorReplaceAttribute overload with {arguments.Count} parameters")
},
"AspectMethodReplaceAttribute" => arguments.Length switch
"AspectMethodReplaceAttribute" => arguments.Count switch
{
// AspectMethodReplaceAttribute(string targetMethod)
1 => $"[AspectMethodReplace({arguments[0]},\"\",[0],[False],[None],Default,[])]",
1 => $"[AspectMethodReplace({arguments[0]},\"\",[0],[False],[None],Default,[]){version}]",
// AspectMethodReplaceAttribute(string targetMethod, params AspectFilter[] filters)
2 => $"[AspectMethodReplace({arguments[0]},\"\",[0],[False],{Check(arguments[1], "[None]")},Default,[])]",
2 => $"[AspectMethodReplace({arguments[0]},\"\",[0],[False],{Check(arguments[1], "[None]")},Default,[]){version}]",
// AspectMethodReplaceAttribute(string targetMethod, string targetType, params AspectFilter[] filters)
3 => arguments[1] switch
{
{ } when arguments[1].StartsWith("[") => $"[AspectMethodReplace({arguments[0]},\"\",{arguments[1]},{arguments[2]},[None],Default,[])]",
{ } when arguments[1].StartsWith("[") => $"[AspectMethodReplace({arguments[0]},\"\",{arguments[1]},{arguments[2]},[None],Default,[]){version}]",
// AspectMethodReplaceAttribute(string targetMethod, string targetType, params AspectFilter[] filters)
_ => $"[AspectMethodReplace({arguments[0]},{arguments[1]},[0],[False],{Check(arguments[2], "[None]")},Default,[])]",
_ => $"[AspectMethodReplace({arguments[0]},{arguments[1]},[0],[False],{Check(arguments[2], "[None]")},Default,[]){version}]",
},
_ => throw new ArgumentException($"Could not find AspectMethodReplaceAttribute overload with {arguments.Length} parameters")
_ => throw new ArgumentException($"Could not find AspectMethodReplaceAttribute overload with {arguments.Count} parameters")
},
"AspectMethodInsertBeforeAttribute" => arguments.Length switch
"AspectMethodInsertBeforeAttribute" => arguments.Count switch
{
// AspectMethodInsertBeforeAttribute(string targetMethod, params int[] paramShift)
2 => $"[AspectMethodInsertBefore({arguments[0]},\"\",{MakeSameSize(Check(arguments[1]))},[None],Default,[])]",
2 => $"[AspectMethodInsertBefore({arguments[0]},\"\",{MakeSameSize(Check(arguments[1]))},[None],Default,[]){version}]",
// AspectMethodInsertBeforeAttribute(string targetMethod, int[] paramShift, bool[] boxParam)
3 => $"[AspectMethodInsertBefore({arguments[0]},\"\",[{arguments[1]}],[{arguments[2]}],[None],Default,[])]",
_ => throw new ArgumentException($"Could not find AspectMethodInsertBeforeAttribute overload with {arguments.Length} parameters")
3 => $"[AspectMethodInsertBefore({arguments[0]},\"\",[{arguments[1]}],[{arguments[2]}],[None],Default,[]){version}]",
_ => throw new ArgumentException($"Could not find AspectMethodInsertBeforeAttribute overload with {arguments.Count} parameters")
},
"AspectMethodInsertAfterAttribute" => arguments.Length switch
"AspectMethodInsertAfterAttribute" => arguments.Count switch
{
// AspectMethodInsertAfterAttribute(string targetMethod)
1 => $"[AspectMethodInsertAfter({arguments[0]},\"\",[0],[False],[None],Default,[])]",
1 => $"[AspectMethodInsertAfter({arguments[0]},\"\",[0],[False],[None],Default,[]){version}]",
// AspectMethodInsertAfterAttribute(string targetMethod, AspectType aspectType, params VulnerabilityType[] vulnerabilityTypes)
3 => $"[AspectMethodInsertAfter({arguments[0]},\"\",[0],[False],[None],{arguments[1]},{Check(arguments[2])})]",
_ => throw new ArgumentException($"Could not find AspectMethodInsertAfterAttribute overload with {arguments.Length} parameters")
3 => $"[AspectMethodInsertAfter({arguments[0]},\"\",[0],[False],[None],{arguments[1]},{Check(arguments[2])}){version}]",
_ => throw new ArgumentException($"Could not find AspectMethodInsertAfterAttribute overload with {arguments.Count} parameters")
},
_ => throw new Exception()
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@
<ItemGroup>
<Compile Include="..\Datadog.Trace\ClrProfiler\InstrumentationCategory.cs" Link="InstrumentationDefinitions\InstrumentationCategory.cs" />
<Compile Include="..\Datadog.Trace\Iast\Dataflow\AspectType.cs" Link="AspectsDefinitions\Sources\AspectType.cs" />
<EmbeddedResource Include="..\Datadog.Trace\Iast\Dataflow\AspectAttribute.cs" Link="AspectsDefinitions\Sources\AspectAttribute.cs" />
<EmbeddedResource Include="..\Datadog.Trace\Iast\Dataflow\AspectClassAttribute.cs" Link="AspectsDefinitions\Sources\AspectClassAttribute.cs" />
<EmbeddedResource Include="..\Datadog.Trace\Iast\Dataflow\AspectCtorReplaceAttribute.cs" Link="AspectsDefinitions\Sources\AspectCtorReplaceAttribute.cs" />
<Compile Include="..\Datadog.Trace\Iast\Dataflow\AspectFilter.cs" Link="AspectsDefinitions\Sources\AspectFilter.cs" />
<EmbeddedResource Include="..\Datadog.Trace\Iast\Dataflow\AspectMethodInsertAfterAttribute.cs" Link="AspectsDefinitions\Sources\AspectMethodInsertAfterAttribute.cs" />
<EmbeddedResource Include="..\Datadog.Trace\Iast\Dataflow\AspectMethodInsertBeforeAttribute.cs" Link="AspectsDefinitions\Sources\AspectMethodInsertBeforeAttribute.cs" />
<EmbeddedResource Include="..\Datadog.Trace\Iast\Dataflow\AspectMethodReplaceAttribute.cs" Link="AspectsDefinitions\Sources\AspectMethodReplaceAttribute.cs" />
<Compile Include="..\Datadog.Trace\Iast\VulnerabilityType.cs" Link="AspectsDefinitions\Sources\VulnerabilityType.cs" />
<Compile Include="..\Datadog.Trace\Iast\VulnerabilityTypeName.cs" Link="AspectsDefinitions\Sources\VulnerabilityTypeName.cs" />
<Compile Include="..\Datadog.Trace\Vendors\MessagePack\Attributes.cs">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,9 @@ internal static partial class AspectDefinitions
"[AspectClass(\"System.Private.Corelib;System.Runtime\",[None],Sink,[Ssrf])] Datadog.Trace.Iast.Aspects.System.Net.WebUtilityAspect",
" [AspectMethodReplace(\"System.Net.WebUtility::HtmlEncode(System.String)\",\"\",[0],[False],[None],Default,[])] Review(System.String)",
"[AspectClass(\"System.Text.Json\",[None],Source,[])] Datadog.Trace.Iast.Aspects.System.Text.Json.JsonDocumentAspects",
" [AspectMethodReplace(\"System.Text.Json.JsonDocument::Parse(System.String,System.Text.Json.JsonDocumentOptions)\",\"\",[0],[False],[None],Default,[])] Parse(System.String,System.Text.Json.JsonDocumentOptions)",
" [AspectMethodReplace(\"System.Text.Json.JsonElement::GetString()\",\"\",[0],[True],[None],Default,[])] GetString(System.Object)",
" [AspectMethodReplace(\"System.Text.Json.JsonElement::GetRawText()\",\"\",[0],[True],[None],Default,[])] GetRawText(System.Object)",
" [AspectMethodReplace(\"System.Text.Json.JsonDocument::Parse(System.String,System.Text.Json.JsonDocumentOptions)\",\"\",[0],[False],[None],Default,[]);V2.49.0] Parse(System.String,System.Text.Json.JsonDocumentOptions)",
" [AspectMethodReplace(\"System.Text.Json.JsonElement::GetString()\",\"\",[0],[True],[None],Default,[]);V2.49.0] GetString(System.Object)",
" [AspectMethodReplace(\"System.Text.Json.JsonElement::GetRawText()\",\"\",[0],[True],[None],Default,[]);V2.49.0] GetRawText(System.Object)",
"[AspectClass(\"System.Web\",[None],Sink,[UnvalidatedRedirect])] Datadog.Trace.Iast.Aspects.System.Web.HttpResponseAspect",
" [AspectMethodInsertBefore(\"System.Web.HttpResponse::Redirect(System.String)\",\"\",[0],[False],[None],Default,[])] Redirect(System.String)",
" [AspectMethodInsertBefore(\"System.Web.HttpResponse::Redirect(System.String,System.Boolean)\",\"\",[1],[False],[None],Default,[])] Redirect(System.String)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,9 @@ internal static partial class AspectDefinitions
"[AspectClass(\"System.Private.Corelib;System.Runtime\",[None],Sink,[Ssrf])] Datadog.Trace.Iast.Aspects.System.Net.WebUtilityAspect",
" [AspectMethodReplace(\"System.Net.WebUtility::HtmlEncode(System.String)\",\"\",[0],[False],[None],Default,[])] Review(System.String)",
"[AspectClass(\"System.Text.Json\",[None],Source,[])] Datadog.Trace.Iast.Aspects.System.Text.Json.JsonDocumentAspects",
" [AspectMethodReplace(\"System.Text.Json.JsonDocument::Parse(System.String,System.Text.Json.JsonDocumentOptions)\",\"\",[0],[False],[None],Default,[])] Parse(System.String,System.Text.Json.JsonDocumentOptions)",
" [AspectMethodReplace(\"System.Text.Json.JsonElement::GetString()\",\"\",[0],[True],[None],Default,[])] GetString(System.Object)",
" [AspectMethodReplace(\"System.Text.Json.JsonElement::GetRawText()\",\"\",[0],[True],[None],Default,[])] GetRawText(System.Object)",
" [AspectMethodReplace(\"System.Text.Json.JsonDocument::Parse(System.String,System.Text.Json.JsonDocumentOptions)\",\"\",[0],[False],[None],Default,[]);V2.49.0] Parse(System.String,System.Text.Json.JsonDocumentOptions)",
" [AspectMethodReplace(\"System.Text.Json.JsonElement::GetString()\",\"\",[0],[True],[None],Default,[]);V2.49.0] GetString(System.Object)",
" [AspectMethodReplace(\"System.Text.Json.JsonElement::GetRawText()\",\"\",[0],[True],[None],Default,[]);V2.49.0] GetRawText(System.Object)",
"[AspectClass(\"System.Web\",[None],Sink,[UnvalidatedRedirect])] Datadog.Trace.Iast.Aspects.System.Web.HttpResponseAspect",
" [AspectMethodInsertBefore(\"System.Web.HttpResponse::Redirect(System.String)\",\"\",[0],[False],[None],Default,[])] Redirect(System.String)",
" [AspectMethodInsertBefore(\"System.Web.HttpResponse::Redirect(System.String,System.Boolean)\",\"\",[1],[False],[None],Default,[])] Redirect(System.String)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class JsonDocumentAspects
/// <param name="json">the JsonDocument result of Parse</param>
/// <param name="options">the JsonDocumentOptions</param>
/// <returns>the JsonDocument result</returns>
[AspectMethodReplace("System.Text.Json.JsonDocument::Parse(System.String,System.Text.Json.JsonDocumentOptions)")]
[AspectMethodReplaceFromVersion("2.49.0", "System.Text.Json.JsonDocument::Parse(System.String,System.Text.Json.JsonDocumentOptions)")]
public static object Parse(string json, JsonDocumentOptions options)
{
var doc = JsonDocument.Parse(json, options);
Expand All @@ -52,7 +52,7 @@ public static object Parse(string json, JsonDocumentOptions options)
/// </summary>
/// <param name="target">the JsonElement instance</param>
/// <returns>the string result</returns>
[AspectMethodReplace("System.Text.Json.JsonElement::GetString()", [0], [true])]
[AspectMethodReplaceFromVersion("2.49.0", "System.Text.Json.JsonElement::GetString()", [0], [true])]
public static string? GetString(object target)
#pragma warning disable DD0005 // Function is already safe where needed
{
Expand Down Expand Up @@ -93,7 +93,7 @@ public static object Parse(string json, JsonDocumentOptions options)
/// </summary>
/// <param name="target">the JsonElement instance</param>
/// <returns>the raw string result</returns>
[AspectMethodReplace("System.Text.Json.JsonElement::GetRawText()", [0], [true])]
[AspectMethodReplaceFromVersion("2.49.0", "System.Text.Json.JsonElement::GetRawText()", [0], [true])]
public static string? GetRawText(object target)
#pragma warning disable DD0005 // Function is already safe where needed
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
namespace Datadog.Trace.Iast.Dataflow;

[AttributeUsage(AttributeTargets.Class)]
internal sealed class AspectClassAttribute : Attribute
internal class AspectClassAttribute : Attribute
{
private readonly List<object> parameters = new List<object>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// <copyright file="AspectClassFromVersionAttribute.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable
using System;
using System.Collections.Generic;

namespace Datadog.Trace.Iast.Dataflow;

[AttributeUsage(AttributeTargets.Class)]
internal sealed class AspectClassFromVersionAttribute : AspectClassAttribute
{
private readonly List<object> parameters = new List<object>();

public AspectClassFromVersionAttribute(string version)
: base()
{
}

public AspectClassFromVersionAttribute(string version, string defaultAssembly)
: base(defaultAssembly)
{
}

public AspectClassFromVersionAttribute(string version, string defaultAssembly, AspectType defaultAspectType, params VulnerabilityType[] defaultVulnerabilityTypes)
: base(defaultAssembly, defaultAspectType, defaultVulnerabilityTypes)
{
}

public AspectClassFromVersionAttribute(string version, string defaultAssembly, AspectFilter[] filters, AspectType defaultAspectType = AspectType.Propagation, params VulnerabilityType[] defaultVulnerabilityTypes)
: base(defaultAssembly, filters, defaultAspectType, defaultVulnerabilityTypes)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace Datadog.Trace.Iast.Dataflow;

[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal sealed class AspectCtorReplaceAttribute : AspectAttribute
internal class AspectCtorReplaceAttribute : AspectAttribute
{
public AspectCtorReplaceAttribute(string targetMethod)
: base(targetMethod, string.Empty, new int[0], new bool[0], new AspectFilter[0], AspectType.Default)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// <copyright file="AspectCtorReplaceFromVersionAttribute.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

using Datadog.Trace.AppSec.Waf.ReturnTypes.Managed;

namespace Datadog.Trace.Iast.Dataflow;

[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal class AspectCtorReplaceFromVersionAttribute : AspectCtorReplaceAttribute
{
public AspectCtorReplaceFromVersionAttribute(string version, string targetMethod)
: base(targetMethod)
{
}

public AspectCtorReplaceFromVersionAttribute(string version, string targetMethod, params AspectFilter[] filters)
: base(targetMethod, filters)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace Datadog.Trace.Iast.Dataflow;

[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal sealed class AspectMethodInsertAfterAttribute : AspectAttribute
internal class AspectMethodInsertAfterAttribute : AspectAttribute
{
public AspectMethodInsertAfterAttribute(string targetMethod)
: base(targetMethod, string.Empty, new int[0], new bool[0], new AspectFilter[0], AspectType.Default)
Expand Down
Loading

0 comments on commit 2bd1621

Please sign in to comment.