Skip to content

Commit

Permalink
Merge pull request #642 from PowershellFrameworkCollective/typeconverter
Browse files Browse the repository at this point in the history
Typeconverter
  • Loading branch information
FriedrichWeinmann authored Sep 17, 2024
2 parents e562a97 + 18c920a commit 01add12
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 37 deletions.
2 changes: 1 addition & 1 deletion PSFramework/PSFramework.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
RootModule = 'PSFramework.psm1'

# Version number of this module.
ModuleVersion = '1.11.343'
ModuleVersion = '1.12.345'

# ID used to uniquely identify this module
GUID = '8028b914-132b-431f-baa9-94a6952f21ff'
Expand Down
Binary file modified PSFramework/bin/PSFramework.dll
Binary file not shown.
Binary file modified PSFramework/bin/PSFramework.pdb
Binary file not shown.
14 changes: 14 additions & 0 deletions PSFramework/bin/PSFramework.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions PSFramework/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# CHANGELOG

## 1.12.345 (2024-09-17)

> Breaking Change
SerializationTypeConverter changed from using BinaryFormatter to using DataContractSerializer instead, avoiding a critical security vulnerability. This change will _not_ affect anybody not using this component to prevent Deserialized objects when sending objects from formal classes from one PowerShell process to another (e.g. with remoting). Regular PowerShell execution - including remoting - remains unaffected (only without the vulnerability).

Actual impact on modules implementing this component:

- "Failure" always means a fallback to "Deserialized." objects, not actual exceptions.
- The new version must be deployed on both ends of the communication, otherwise implemented deserialization will fail.
- The new version will fail to import clixml files exported with the old version
- All sub-properties must adhere to the serialization rules, not just the top level class. Previously it was possible to have your own class have an "object"-typed property and only the content of that property would be a "deserialized." object, rather the entire item. This no longer works.

This critical security vulnerability superseded the reliability promise, but should fortunately have little impact on almost all existing use of the module.

> Change List
- Sec: Critical security update to the `SerializationTypeConverter` class and PS Object Serialization extension component.
- Fix: ConvertTo-PSFHashtable - `-Remap` fails when trying to fix the casing on a key. (#641)

## 1.11.343 (2024-07-18)

- Fix: Disable-PSFLoggingProvider - fails with timeout error.
Expand Down
3 changes: 2 additions & 1 deletion library/PSFramework/Commands/ConvertToPSFHashtableCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,9 @@ protected override void ProcessRecord()
{
if (result.ContainsKey(key))
{
result[Remap[key]] = result[key];
object value = result[key];
result.Remove(key);
result[Remap[key]] = value;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions library/PSFramework/PSFramework.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<HintPath>C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
Expand Down
127 changes: 92 additions & 35 deletions library/PSFramework/Serialization/SerializationTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.IO.Compression;
using System.Management.Automation;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;

namespace PSFramework.Serialization
{
Expand All @@ -14,7 +17,7 @@ namespace PSFramework.Serialization
/// </summary>
public class SerializationTypeConverter : PSTypeConverter
{
private static ResolveEventHandler AssemblyHandler = new ResolveEventHandler(SerializationTypeConverter.CurrentDomain_AssemblyResolve);
private static ResolveEventHandler AssemblyHandler = new ResolveEventHandler(CurrentDomain_AssemblyResolve);

/// <summary>
/// Whether the source can be converted to its destination
Expand Down Expand Up @@ -87,7 +90,7 @@ private bool CanConvert(object sourceValue, Type destinationType, out byte[] ser
error = new NotSupportedException(string.Format("Unsupported Source Type: {0}", sourceValue.GetType().FullName));
return false;
}
if (!SerializationTypeConverter.CanSerialize(destinationType))
if (!CanSerialize(destinationType))
{
error = new NotSupportedException(string.Format("Unsupported Type Conversion: {0}", destinationType.FullName));
return false;
Expand Down Expand Up @@ -125,24 +128,19 @@ private object DeserializeObject(object sourceValue, Type destinationType)
throw ex;
}
object obj;
using (MemoryStream memoryStream = new MemoryStream(buffer))

AppDomain.CurrentDomain.AssemblyResolve += AssemblyHandler;
try
{
AppDomain.CurrentDomain.AssemblyResolve += SerializationTypeConverter.AssemblyHandler;
try
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
obj = binaryFormatter.Deserialize(memoryStream);
PSFCore.PSFCoreHost.WriteDebug("Serializer.DeserializeObject.Obj", obj);
IDeserializationCallback deserializationCallback = obj as IDeserializationCallback;
if (deserializationCallback != null)
{
deserializationCallback.OnDeserialization(sourceValue);
}
}
finally
{
AppDomain.CurrentDomain.AssemblyResolve -= SerializationTypeConverter.AssemblyHandler;
}
obj = ConvertFromXml(ExpandString(buffer), destinationType);
PSFCore.PSFCoreHost.WriteDebug("Serializer.DeserializeObject.Obj", obj);
IDeserializationCallback deserializationCallback = obj as IDeserializationCallback;
if (deserializationCallback != null)
deserializationCallback.OnDeserialization(sourceValue);
}
finally
{
AppDomain.CurrentDomain.AssemblyResolve -= AssemblyHandler;
}
return obj;
}
Expand All @@ -152,13 +150,13 @@ private object DeserializeObject(object sourceValue, Type destinationType)
/// </summary>
public static void RegisterAssemblyResolver()
{
AppDomain.CurrentDomain.AssemblyResolve += SerializationTypeConverter.AssemblyHandler;
AppDomain.CurrentDomain.AssemblyResolve += AssemblyHandler;
}
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
PSFCore.PSFCoreHost.WriteDebug("Serializer.AssemblyResolve.Sender", sender);
PSFCore.PSFCoreHost.WriteDebug("Serializer.AssemblyResolve.Args", args);

// 1) Match directly against existing assembly
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
Expand All @@ -169,7 +167,7 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven
string shortName = args.Name.Split(',')[0];
if (AssemblyShortnameMapping.Count > 0 && AssemblyShortnameMapping[shortName])
for (int i = 0; i < assemblies.Length; i++)
if (String.Equals(assemblies[i].FullName.Split(',')[0],shortName, StringComparison.InvariantCultureIgnoreCase))
if (String.Equals(assemblies[i].FullName.Split(',')[0], shortName, StringComparison.InvariantCultureIgnoreCase))
return assemblies[i];

if (AssemblyMapping.Count == 0)
Expand Down Expand Up @@ -201,7 +199,7 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven
/// <returns>Whether the object can be serialized</returns>
public static bool CanSerialize(object obj)
{
return obj != null && SerializationTypeConverter.CanSerialize(obj.GetType());
return obj != null && CanSerialize(obj.GetType());
}

/// <summary>
Expand All @@ -211,7 +209,7 @@ public static bool CanSerialize(object obj)
/// <returns>Whether the specified type can be serialized</returns>
public static bool CanSerialize(Type type)
{
return SerializationTypeConverter.TypeIsSerializable(type) && !type.IsEnum || (type.Equals(typeof(Exception)) || type.IsSubclassOf(typeof(Exception)));
return TypeIsSerializable(type) && !type.IsEnum || (type.Equals(typeof(Exception)) || type.IsSubclassOf(typeof(Exception)));
}

/// <summary>
Expand All @@ -237,7 +235,7 @@ public static bool TypeIsSerializable(Type type)
for (int i = 0; i < genericArguments.Length; i++)
{
Type type2 = genericArguments[i];
if (!SerializationTypeConverter.TypeIsSerializable(type2))
if (!TypeIsSerializable(type2))
{
return false;
}
Expand All @@ -252,16 +250,9 @@ public static bool TypeIsSerializable(Type type)
/// <returns>A memory stream.</returns>
public static object GetSerializationData(PSObject psObject)
{
object result;
using (MemoryStream memoryStream = new MemoryStream())
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, psObject.BaseObject);
result = memoryStream.ToArray();
}
return result;
return CompressString(ConvertToXml(psObject.BaseObject));
}

/// <summary>
/// Allows remapping assembly-names for objects being deserialized, using the full assembly-name.
/// </summary>
Expand All @@ -270,5 +261,71 @@ public static object GetSerializationData(PSObject psObject)
/// Allows remapping assembly-names for objects being deserialized, using an abbreviated name only, to help avoid having to be version specific.
/// </summary>
public static readonly ConcurrentDictionary<string, bool> AssemblyShortnameMapping = new ConcurrentDictionary<string, bool>(StringComparer.InvariantCultureIgnoreCase);

public static string ConvertToXml(object Item)
{
if (Item == null)
throw new ArgumentNullException("item");

string result;
using (StringWriter writer = new StringWriter())
using (XmlTextWriter xmlWriter = new XmlTextWriter(writer))
{
DataContractSerializer serializer = new DataContractSerializer(Item.GetType());
serializer.WriteObject(xmlWriter, Item);
result = writer.ToString();
}
return result;
}

public static object ConvertFromXml(string Xml, Type ExpectedType)
{
object result;
using (StringReader reader = new StringReader(Xml))
using (XmlTextReader xmlReader = new XmlTextReader(reader))
{
DataContractSerializer serializer = new DataContractSerializer(ExpectedType);
result = serializer.ReadObject(xmlReader);
}
return result;
}

/// <summary>
/// Compress string using default zip algorithms
/// </summary>
/// <param name="String">The string to compress</param>
/// <returns>Returns a compressed string as byte-array.</returns>
public static byte[] CompressString(string String)
{
byte[] bytes = Encoding.UTF8.GetBytes(String);
using (MemoryStream outputStream = new MemoryStream())
using (GZipStream gZipStream = new GZipStream(outputStream, CompressionMode.Compress))
{
gZipStream.Write(bytes, 0, bytes.Length);
gZipStream.Close();
outputStream.Close();
return outputStream.ToArray();
}
}

/// <summary>
/// Expand a string using default zig algorithms
/// </summary>
/// <param name="CompressedString">The compressed string to expand</param>
/// <returns>Returns an expanded string.</returns>
public static string ExpandString(byte[] CompressedString)
{
using (MemoryStream inputStream = new MemoryStream(CompressedString))
using (MemoryStream outputStream = new MemoryStream())
using (GZipStream converter = new GZipStream(inputStream, CompressionMode.Decompress))
{
converter.CopyTo(outputStream);
converter.Close();
inputStream.Close();
string result = Encoding.UTF8.GetString(outputStream.ToArray());
outputStream.Close();
return result;
}
}
}
}

0 comments on commit 01add12

Please sign in to comment.