Skip to content
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
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;
}
}
}
}