Skip to content

Commit

Permalink
Add BinaryFormatter auditing EventSource (dotnet#39874)
Browse files Browse the repository at this point in the history
  • Loading branch information
GrabYourPitchforks authored and Jacksondr5 committed Aug 10, 2020
1 parent 2712076 commit d6976aa
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
<Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryFormatter.cs" />
<Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryFormatter.Core.cs" Condition="'$(TargetsBrowser)' != 'true'" />
<Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryFormatter.PlatformNotSupported.cs" Condition="'$(TargetsBrowser)' == 'true'" />
<Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryFormatterEventSource.cs" />
<Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryFormatterWriter.cs" />
<Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryObjectInfo.cs" />
<Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryObjectReader.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public object Deserialize(Stream serializationStream)
};
try
{
BinaryFormatterEventSource.Log.DeserializationStart();
var parser = new BinaryParser(serializationStream, reader);
return reader.Deserialize(parser);
}
Expand All @@ -50,6 +51,10 @@ public object Deserialize(Stream serializationStream)
{
throw new SerializationException(SR.Serialization_CorruptedStream, e);
}
finally
{
BinaryFormatterEventSource.Log.DeserializationStop();
}
}

[Obsolete(Obsoletions.BinaryFormatterMessage, DiagnosticId = Obsoletions.BinaryFormatterDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
Expand All @@ -73,10 +78,18 @@ public void Serialize(Stream serializationStream, object graph)
_assemblyFormat = _assemblyFormat,
};

var sow = new ObjectWriter(_surrogates, _context, formatterEnums, _binder);
BinaryFormatterWriter binaryWriter = new BinaryFormatterWriter(serializationStream, sow, _typeFormat);
sow.Serialize(graph, binaryWriter);
_crossAppDomainArray = sow._crossAppDomainArray;
try
{
BinaryFormatterEventSource.Log.SerializationStart();
var sow = new ObjectWriter(_surrogates, _context, formatterEnums, _binder);
BinaryFormatterWriter binaryWriter = new BinaryFormatterWriter(serializationStream, sow, _typeFormat);
sow.Serialize(graph, binaryWriter);
_crossAppDomainArray = sow._crossAppDomainArray;
}
finally
{
BinaryFormatterEventSource.Log.SerializationStop();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.Tracing;

namespace System.Runtime.Serialization.Formatters.Binary
{
[EventSource(
Name = "System.Runtime.Serialization.Formatters.Binary.BinaryFormatterEventSource")]
internal sealed class BinaryFormatterEventSource : EventSource
{
private const int EventId_SerializationStart = 10;
private const int EventId_SerializationStop = 11;
private const int EventId_SerializingObject = 12;
private const int EventId_DeserializationStart = 20;
private const int EventId_DeserializationStop = 21;
private const int EventId_DeserializingObject = 22;

public static readonly BinaryFormatterEventSource Log = new BinaryFormatterEventSource();

private BinaryFormatterEventSource()
{
}

[Event(EventId_SerializationStart, Opcode = EventOpcode.Start, Keywords = Keywords.Serialization, Level = EventLevel.Informational, ActivityOptions = EventActivityOptions.Recursive)]
public void SerializationStart()
{
if (IsEnabled(EventLevel.Informational, Keywords.Serialization))
{
WriteEvent(EventId_SerializationStart);
}
}

[Event(EventId_SerializationStop, Opcode = EventOpcode.Stop, Keywords = Keywords.Serialization, Level = EventLevel.Informational)]
public void SerializationStop()
{
if (IsEnabled(EventLevel.Informational, Keywords.Serialization))
{
WriteEvent(EventId_SerializationStop);
}
}

[NonEvent]
public void SerializingObject(Type type)
{
Debug.Assert(type != null);

if (IsEnabled(EventLevel.Informational, Keywords.Serialization))
{
SerializingObject(type.AssemblyQualifiedName);
}
}

[Event(EventId_SerializingObject, Keywords = Keywords.Serialization, Level = EventLevel.Informational)]
private void SerializingObject(string? typeName)
{
WriteEvent(EventId_SerializingObject, typeName);
}

[Event(EventId_DeserializationStart, Opcode = EventOpcode.Start, Keywords = Keywords.Deserialization, Level = EventLevel.Informational, ActivityOptions = EventActivityOptions.Recursive)]
public void DeserializationStart()
{
if (IsEnabled(EventLevel.Informational, Keywords.Deserialization))
{
WriteEvent(EventId_DeserializationStart);
}
}

[Event(EventId_DeserializationStop, Opcode = EventOpcode.Stop, Keywords = Keywords.Deserialization, Level = EventLevel.Informational)]
public void DeserializationStop()
{
if (IsEnabled(EventLevel.Informational, Keywords.Deserialization))
{
WriteEvent(EventId_DeserializationStop);
}
}

[NonEvent]
public void DeserializingObject(Type type)
{
Debug.Assert(type != null);

if (IsEnabled(EventLevel.Informational, Keywords.Deserialization))
{
DeserializingObject(type.AssemblyQualifiedName);
}
}

[Event(EventId_DeserializingObject, Keywords = Keywords.Deserialization, Level = EventLevel.Informational)]
private void DeserializingObject(string? typeName)
{
WriteEvent(EventId_DeserializingObject, typeName);
}

public class Keywords
{
public const EventKeywords Serialization = (EventKeywords)1;
public const EventKeywords Deserialization = (EventKeywords)2;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,11 @@ private void InitMemberInfo()

internal string GetAssemblyString() => _binderAssemblyString ?? _cache._assemblyString;

private void InvokeSerializationBinder(SerializationBinder? binder) =>
private void InvokeSerializationBinder(SerializationBinder? binder)
{
BinaryFormatterEventSource.Log.SerializingObject(_objectType!);
binder?.BindToName(_objectType!, out _binderAssemblyString, out _binderTypeName);
}

internal void GetMemberInfo(out string[]? outMemberNames, out Type[]? outMemberTypes, out object?[]? outMemberData)
{
Expand Down Expand Up @@ -392,6 +395,8 @@ internal void Init(Type? objectType, string[] memberNames, Type[]? memberTypes,

private void InitReadConstructor(Type objectType, ISurrogateSelector? surrogateSelector, StreamingContext context)
{
BinaryFormatterEventSource.Log.DeserializingObject(objectType);

if (objectType.IsArray)
{
InitNoMembers();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using Xunit;

namespace System.Runtime.Serialization.Formatters.Tests
{
[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))]
public static class BinaryFormatterEventSourceTests
{
private const string BinaryFormatterEventSourceName = "System.Runtime.Serialization.Formatters.Binary.BinaryFormatterEventSource";

[Fact]
public static void RecordsSerialization()
{
using LoggingEventListener listener = new LoggingEventListener();

BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(Stream.Null, CreatePerson());
string[] capturedLog = listener.CaptureLog();

string[] expected = new string[]
{
"SerializationStart [Start, 00000001]: <no payload>",
"SerializingObject [Info, 00000001]: " + typeof(Person).AssemblyQualifiedName,
"SerializingObject [Info, 00000001]: " + typeof(Address).AssemblyQualifiedName,
"SerializationStop [Stop, 00000001]: <no payload>",
};

Assert.Equal(expected, capturedLog);
}

[Fact]
public static void RecordsDeserialization()
{
MemoryStream ms = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, CreatePerson());
ms.Position = 0;

using LoggingEventListener listener = new LoggingEventListener();
formatter.Deserialize(ms);
string[] capturedLog = listener.CaptureLog();

string[] expected = new string[]
{
"DeserializationStart [Start, 00000002]: <no payload>",
"DeserializingObject [Info, 00000002]: " + typeof(Person).AssemblyQualifiedName,
"DeserializingObject [Info, 00000002]: " + typeof(Address).AssemblyQualifiedName,
"DeserializationStop [Stop, 00000002]: <no payload>",
};

Assert.Equal(expected, capturedLog);
}

[Fact]
public static void RecordsNestedSerializationCalls()
{
// First, serialization

using LoggingEventListener listener = new LoggingEventListener();

MemoryStream ms = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, new ClassWithNestedDeserialization());
string[] capturedLog = listener.CaptureLog();
ms.Position = 0;

string[] expected = new string[]
{
"SerializationStart [Start, 00000001]: <no payload>",
"SerializingObject [Info, 00000001]: " + typeof(ClassWithNestedDeserialization).AssemblyQualifiedName,
"SerializationStart [Start, 00000001]: <no payload>",
"SerializingObject [Info, 00000001]: " + typeof(Address).AssemblyQualifiedName,
"SerializationStop [Stop, 00000001]: <no payload>",
"SerializationStop [Stop, 00000001]: <no payload>",
};

Assert.Equal(expected, capturedLog);
listener.ClearLog();

// Then, deserialization

ms.Position = 0;
formatter.Deserialize(ms);
capturedLog = listener.CaptureLog();

expected = new string[]
{
"DeserializationStart [Start, 00000002]: <no payload>",
"DeserializingObject [Info, 00000002]: " + typeof(ClassWithNestedDeserialization).AssemblyQualifiedName,
"DeserializationStart [Start, 00000002]: <no payload>",
"DeserializingObject [Info, 00000002]: " + typeof(Address).AssemblyQualifiedName,
"DeserializationStop [Stop, 00000002]: <no payload>",
"DeserializationStop [Stop, 00000002]: <no payload>",
};

Assert.Equal(expected, capturedLog);
}

private static Person CreatePerson()
{
return new Person()
{
Name = "Some Chap",
HomeAddress = new Address()
{
Street = "123 Anywhere Ln",
City = "Anywhere ST 00000 United States"
}
};
}

private sealed class LoggingEventListener : EventListener
{
private readonly Thread _activeThread = Thread.CurrentThread;
private readonly List<string> _log = new List<string>();

private void AddToLog(FormattableString message)
{
_log.Add(FormattableString.Invariant(message));
}

// Captures the current log
public string[] CaptureLog()
{
return _log.ToArray();
}

public void ClearLog()
{
_log.Clear();
}

protected override void OnEventSourceCreated(EventSource eventSource)
{
if (eventSource.Name == BinaryFormatterEventSourceName)
{
EnableEvents(eventSource, EventLevel.Verbose);
}

base.OnEventSourceCreated(eventSource);
}

protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
// The test project is parallelized. We want to filter to only events that fired
// on the current thread, otherwise we could throw off the test results.

if (Thread.CurrentThread != _activeThread)
{
return;
}

AddToLog($"{eventData.EventName} [{eventData.Opcode}, {(int)eventData.Keywords & int.MaxValue:X8}]: {ParsePayload(eventData.Payload)}");
base.OnEventWritten(eventData);
}

private static string ParsePayload(IReadOnlyCollection<object> collection)
{
if (collection?.Count > 0)
{
return string.Join("; ", collection.Select(o => o?.ToString() ?? "<null>"));
}
else
{
return "<no payload>";
}
}
}

[Serializable]
private class Person
{
public string Name { get; set; }
public Address HomeAddress { get; set; }
}

[Serializable]
private class Address
{
public string Street { get; set; }
public string City { get; set; }
}

[Serializable]
public class ClassWithNestedDeserialization : ISerializable
{
public ClassWithNestedDeserialization()
{
}

protected ClassWithNestedDeserialization(SerializationInfo info, StreamingContext context)
{
byte[] serializedData = (byte[])info.GetValue("SomeField", typeof(byte[]));
MemoryStream ms = new MemoryStream(serializedData);
BinaryFormatter formatter = new BinaryFormatter();
formatter.Deserialize(ms); // should deserialize an 'Address' instance
}

public void GetObjectData(SerializationInfo info, StreamingContext context)
{
MemoryStream ms = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, new Address());
info.AddValue("SomeField", ms.ToArray());
}
}
}
}
Loading

0 comments on commit d6976aa

Please sign in to comment.