Skip to content

Commit 01add12

Browse files
Merge pull request #642 from PowershellFrameworkCollective/typeconverter
Typeconverter
2 parents e562a97 + 18c920a commit 01add12

File tree

8 files changed

+130
-37
lines changed

8 files changed

+130
-37
lines changed

PSFramework/PSFramework.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
RootModule = 'PSFramework.psm1'
55

66
# Version number of this module.
7-
ModuleVersion = '1.11.343'
7+
ModuleVersion = '1.12.345'
88

99
# ID used to uniquely identify this module
1010
GUID = '8028b914-132b-431f-baa9-94a6952f21ff'

PSFramework/bin/PSFramework.dll

1 KB
Binary file not shown.

PSFramework/bin/PSFramework.pdb

2 KB
Binary file not shown.

PSFramework/bin/PSFramework.xml

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

PSFramework/changelog.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
# CHANGELOG
22

3+
## 1.12.345 (2024-09-17)
4+
5+
> Breaking Change
6+
7+
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).
8+
9+
Actual impact on modules implementing this component:
10+
11+
- "Failure" always means a fallback to "Deserialized." objects, not actual exceptions.
12+
- The new version must be deployed on both ends of the communication, otherwise implemented deserialization will fail.
13+
- The new version will fail to import clixml files exported with the old version
14+
- 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.
15+
16+
This critical security vulnerability superseded the reliability promise, but should fortunately have little impact on almost all existing use of the module.
17+
18+
> Change List
19+
20+
- Sec: Critical security update to the `SerializationTypeConverter` class and PS Object Serialization extension component.
21+
- Fix: ConvertTo-PSFHashtable - `-Remap` fails when trying to fix the casing on a key. (#641)
22+
323
## 1.11.343 (2024-07-18)
424

525
- Fix: Disable-PSFLoggingProvider - fails with timeout error.

library/PSFramework/Commands/ConvertToPSFHashtableCommand.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,9 @@ protected override void ProcessRecord()
182182
{
183183
if (result.ContainsKey(key))
184184
{
185-
result[Remap[key]] = result[key];
185+
object value = result[key];
186186
result.Remove(key);
187+
result[Remap[key]] = value;
187188
}
188189
}
189190
}

library/PSFramework/PSFramework.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
<HintPath>C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll</HintPath>
5454
<Private>False</Private>
5555
</Reference>
56+
<Reference Include="System.Runtime.Serialization" />
5657
<Reference Include="System.Xml.Linq" />
5758
<Reference Include="System.Data.DataSetExtensions" />
5859
<Reference Include="Microsoft.CSharp" />

library/PSFramework/Serialization/SerializationTypeConverter.cs

Lines changed: 92 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.IO;
4+
using System.IO.Compression;
45
using System.Management.Automation;
56
using System.Reflection;
67
using System.Runtime.Serialization;
7-
using System.Runtime.Serialization.Formatters.Binary;
8+
using System.Text;
89
using System.Text.RegularExpressions;
10+
using System.Xml;
11+
using System.Xml.Linq;
912

1013
namespace PSFramework.Serialization
1114
{
@@ -14,7 +17,7 @@ namespace PSFramework.Serialization
1417
/// </summary>
1518
public class SerializationTypeConverter : PSTypeConverter
1619
{
17-
private static ResolveEventHandler AssemblyHandler = new ResolveEventHandler(SerializationTypeConverter.CurrentDomain_AssemblyResolve);
20+
private static ResolveEventHandler AssemblyHandler = new ResolveEventHandler(CurrentDomain_AssemblyResolve);
1821

1922
/// <summary>
2023
/// Whether the source can be converted to its destination
@@ -87,7 +90,7 @@ private bool CanConvert(object sourceValue, Type destinationType, out byte[] ser
8790
error = new NotSupportedException(string.Format("Unsupported Source Type: {0}", sourceValue.GetType().FullName));
8891
return false;
8992
}
90-
if (!SerializationTypeConverter.CanSerialize(destinationType))
93+
if (!CanSerialize(destinationType))
9194
{
9295
error = new NotSupportedException(string.Format("Unsupported Type Conversion: {0}", destinationType.FullName));
9396
return false;
@@ -125,24 +128,19 @@ private object DeserializeObject(object sourceValue, Type destinationType)
125128
throw ex;
126129
}
127130
object obj;
128-
using (MemoryStream memoryStream = new MemoryStream(buffer))
131+
132+
AppDomain.CurrentDomain.AssemblyResolve += AssemblyHandler;
133+
try
129134
{
130-
AppDomain.CurrentDomain.AssemblyResolve += SerializationTypeConverter.AssemblyHandler;
131-
try
132-
{
133-
BinaryFormatter binaryFormatter = new BinaryFormatter();
134-
obj = binaryFormatter.Deserialize(memoryStream);
135-
PSFCore.PSFCoreHost.WriteDebug("Serializer.DeserializeObject.Obj", obj);
136-
IDeserializationCallback deserializationCallback = obj as IDeserializationCallback;
137-
if (deserializationCallback != null)
138-
{
139-
deserializationCallback.OnDeserialization(sourceValue);
140-
}
141-
}
142-
finally
143-
{
144-
AppDomain.CurrentDomain.AssemblyResolve -= SerializationTypeConverter.AssemblyHandler;
145-
}
135+
obj = ConvertFromXml(ExpandString(buffer), destinationType);
136+
PSFCore.PSFCoreHost.WriteDebug("Serializer.DeserializeObject.Obj", obj);
137+
IDeserializationCallback deserializationCallback = obj as IDeserializationCallback;
138+
if (deserializationCallback != null)
139+
deserializationCallback.OnDeserialization(sourceValue);
140+
}
141+
finally
142+
{
143+
AppDomain.CurrentDomain.AssemblyResolve -= AssemblyHandler;
146144
}
147145
return obj;
148146
}
@@ -152,13 +150,13 @@ private object DeserializeObject(object sourceValue, Type destinationType)
152150
/// </summary>
153151
public static void RegisterAssemblyResolver()
154152
{
155-
AppDomain.CurrentDomain.AssemblyResolve += SerializationTypeConverter.AssemblyHandler;
153+
AppDomain.CurrentDomain.AssemblyResolve += AssemblyHandler;
156154
}
157155
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
158156
{
159157
PSFCore.PSFCoreHost.WriteDebug("Serializer.AssemblyResolve.Sender", sender);
160158
PSFCore.PSFCoreHost.WriteDebug("Serializer.AssemblyResolve.Args", args);
161-
159+
162160
// 1) Match directly against existing assembly
163161
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
164162
for (int i = 0; i < assemblies.Length; i++)
@@ -169,7 +167,7 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven
169167
string shortName = args.Name.Split(',')[0];
170168
if (AssemblyShortnameMapping.Count > 0 && AssemblyShortnameMapping[shortName])
171169
for (int i = 0; i < assemblies.Length; i++)
172-
if (String.Equals(assemblies[i].FullName.Split(',')[0],shortName, StringComparison.InvariantCultureIgnoreCase))
170+
if (String.Equals(assemblies[i].FullName.Split(',')[0], shortName, StringComparison.InvariantCultureIgnoreCase))
173171
return assemblies[i];
174172

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

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

217215
/// <summary>
@@ -237,7 +235,7 @@ public static bool TypeIsSerializable(Type type)
237235
for (int i = 0; i < genericArguments.Length; i++)
238236
{
239237
Type type2 = genericArguments[i];
240-
if (!SerializationTypeConverter.TypeIsSerializable(type2))
238+
if (!TypeIsSerializable(type2))
241239
{
242240
return false;
243241
}
@@ -252,16 +250,9 @@ public static bool TypeIsSerializable(Type type)
252250
/// <returns>A memory stream.</returns>
253251
public static object GetSerializationData(PSObject psObject)
254252
{
255-
object result;
256-
using (MemoryStream memoryStream = new MemoryStream())
257-
{
258-
BinaryFormatter binaryFormatter = new BinaryFormatter();
259-
binaryFormatter.Serialize(memoryStream, psObject.BaseObject);
260-
result = memoryStream.ToArray();
261-
}
262-
return result;
253+
return CompressString(ConvertToXml(psObject.BaseObject));
263254
}
264-
255+
265256
/// <summary>
266257
/// Allows remapping assembly-names for objects being deserialized, using the full assembly-name.
267258
/// </summary>
@@ -270,5 +261,71 @@ public static object GetSerializationData(PSObject psObject)
270261
/// Allows remapping assembly-names for objects being deserialized, using an abbreviated name only, to help avoid having to be version specific.
271262
/// </summary>
272263
public static readonly ConcurrentDictionary<string, bool> AssemblyShortnameMapping = new ConcurrentDictionary<string, bool>(StringComparer.InvariantCultureIgnoreCase);
264+
265+
public static string ConvertToXml(object Item)
266+
{
267+
if (Item == null)
268+
throw new ArgumentNullException("item");
269+
270+
string result;
271+
using (StringWriter writer = new StringWriter())
272+
using (XmlTextWriter xmlWriter = new XmlTextWriter(writer))
273+
{
274+
DataContractSerializer serializer = new DataContractSerializer(Item.GetType());
275+
serializer.WriteObject(xmlWriter, Item);
276+
result = writer.ToString();
277+
}
278+
return result;
279+
}
280+
281+
public static object ConvertFromXml(string Xml, Type ExpectedType)
282+
{
283+
object result;
284+
using (StringReader reader = new StringReader(Xml))
285+
using (XmlTextReader xmlReader = new XmlTextReader(reader))
286+
{
287+
DataContractSerializer serializer = new DataContractSerializer(ExpectedType);
288+
result = serializer.ReadObject(xmlReader);
289+
}
290+
return result;
291+
}
292+
293+
/// <summary>
294+
/// Compress string using default zip algorithms
295+
/// </summary>
296+
/// <param name="String">The string to compress</param>
297+
/// <returns>Returns a compressed string as byte-array.</returns>
298+
public static byte[] CompressString(string String)
299+
{
300+
byte[] bytes = Encoding.UTF8.GetBytes(String);
301+
using (MemoryStream outputStream = new MemoryStream())
302+
using (GZipStream gZipStream = new GZipStream(outputStream, CompressionMode.Compress))
303+
{
304+
gZipStream.Write(bytes, 0, bytes.Length);
305+
gZipStream.Close();
306+
outputStream.Close();
307+
return outputStream.ToArray();
308+
}
309+
}
310+
311+
/// <summary>
312+
/// Expand a string using default zig algorithms
313+
/// </summary>
314+
/// <param name="CompressedString">The compressed string to expand</param>
315+
/// <returns>Returns an expanded string.</returns>
316+
public static string ExpandString(byte[] CompressedString)
317+
{
318+
using (MemoryStream inputStream = new MemoryStream(CompressedString))
319+
using (MemoryStream outputStream = new MemoryStream())
320+
using (GZipStream converter = new GZipStream(inputStream, CompressionMode.Decompress))
321+
{
322+
converter.CopyTo(outputStream);
323+
converter.Close();
324+
inputStream.Close();
325+
string result = Encoding.UTF8.GetString(outputStream.ToArray());
326+
outputStream.Close();
327+
return result;
328+
}
329+
}
273330
}
274331
}

0 commit comments

Comments
 (0)