Skip to content

Commit fffed35

Browse files
committed
Add DisablePrivilegedAccess option.
In restricted CAS environment including browser based SIlverlight Application, non-public member access via reflection causes runtime error. This should be opt-out because: * Some people may decide to grant reflection permission for interoperability. * But many people should decide to use public entities/DTOs instead of permission grant. This option gives selection for app developers. This commit also adds unit test in full trust mode.
1 parent ab19121 commit fffed35

27 files changed

Lines changed: 1693 additions & 266 deletions

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,7 @@ Release 0.9.0 (planned)
623623
* Users of serializer code generator API can specify TextWriter to output. This may improve tooling chain.
624624
* Users of serializer code generator API can suppress [DebuggerNonUserCode] attribute to enable debugger step in.
625625
* SerializerRepository API now expose ContainsFor and GetRegisteredSerializers methods to investigate registered serializers.
626+
* SerializationContext.DisablePrivilegedAccess for restricted environment like Silverlight to select between granting permission or relinquish non-public access.
626627

627628
BUG FIXES
628629
* The generated code for the type which has Tuple typed member uses old PackHelper API.
@@ -633,4 +634,5 @@ Release 0.9.0 (planned)
633634
* Fix some built-in serializers throws InvalidOperationException instead of SerializationException for type errors. Issue #204
634635
* Fix a combination of readonly members and collection members incorrect code generation when the type also have deserialization constructor. Issue #207.
635636
* Fix Windows Native build error. Issue #206.
637+
* Fix built-in collection serializers such as List<T> serializer causes SecurityException when the program run in restricted environment like Silverlight. Issue #205.
636638

MsgPack.Windows.sln

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "nunit.framework-sl-5.0", "t
6262
EndProject
6363
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "nunitlite-sl-5.0", "test\NUnitLite\NUnitFramework\nunitlite\nunitlite-sl-5.0.csproj", "{0A5F920A-1BF5-4DAC-B799-0C618B203797}"
6464
EndProject
65+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MsgPack.UnitTest.Silverlight.5.FullTrust", "test\MsgPack.UnitTest.Silverlight.5.FullTrust\MsgPack.UnitTest.Silverlight.5.FullTrust.csproj", "{26A3F930-FDAF-4886-9111-D326A6F68E82}"
66+
EndProject
6567
Global
6668
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6769
CodeAnalysis|Any CPU = CodeAnalysis|Any CPU
@@ -650,6 +652,46 @@ Global
650652
{0A5F920A-1BF5-4DAC-B799-0C618B203797}.Release|x64.Build.0 = Release|Any CPU
651653
{0A5F920A-1BF5-4DAC-B799-0C618B203797}.Release|x86.ActiveCfg = Release|Any CPU
652654
{0A5F920A-1BF5-4DAC-B799-0C618B203797}.Release|x86.Build.0 = Release|Any CPU
655+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU
656+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU
657+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.CodeAnalysis|ARM.ActiveCfg = Release|Any CPU
658+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.CodeAnalysis|ARM.Build.0 = Release|Any CPU
659+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.CodeAnalysis|Mixed Platforms.ActiveCfg = Release|Any CPU
660+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.CodeAnalysis|Mixed Platforms.Build.0 = Release|Any CPU
661+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU
662+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.CodeAnalysis|x64.Build.0 = Release|Any CPU
663+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.CodeAnalysis|x86.ActiveCfg = Release|Any CPU
664+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.CodeAnalysis|x86.Build.0 = Release|Any CPU
665+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
666+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Debug|Any CPU.Build.0 = Debug|Any CPU
667+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Debug|ARM.ActiveCfg = Debug|Any CPU
668+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Debug|ARM.Build.0 = Debug|Any CPU
669+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
670+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
671+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Debug|x64.ActiveCfg = Debug|Any CPU
672+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Debug|x64.Build.0 = Debug|Any CPU
673+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Debug|x86.ActiveCfg = Debug|Any CPU
674+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Debug|x86.Build.0 = Debug|Any CPU
675+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.PerformanceTest|Any CPU.ActiveCfg = Release|Any CPU
676+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.PerformanceTest|Any CPU.Build.0 = Release|Any CPU
677+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.PerformanceTest|ARM.ActiveCfg = Release|Any CPU
678+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.PerformanceTest|ARM.Build.0 = Release|Any CPU
679+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.PerformanceTest|Mixed Platforms.ActiveCfg = Release|Any CPU
680+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.PerformanceTest|Mixed Platforms.Build.0 = Release|Any CPU
681+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.PerformanceTest|x64.ActiveCfg = Release|Any CPU
682+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.PerformanceTest|x64.Build.0 = Release|Any CPU
683+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.PerformanceTest|x86.ActiveCfg = Release|Any CPU
684+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.PerformanceTest|x86.Build.0 = Release|Any CPU
685+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Release|Any CPU.ActiveCfg = Release|Any CPU
686+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Release|Any CPU.Build.0 = Release|Any CPU
687+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Release|ARM.ActiveCfg = Release|Any CPU
688+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Release|ARM.Build.0 = Release|Any CPU
689+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
690+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Release|Mixed Platforms.Build.0 = Release|Any CPU
691+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Release|x64.ActiveCfg = Release|Any CPU
692+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Release|x64.Build.0 = Release|Any CPU
693+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Release|x86.ActiveCfg = Release|Any CPU
694+
{26A3F930-FDAF-4886-9111-D326A6F68E82}.Release|x86.Build.0 = Release|Any CPU
653695
EndGlobalSection
654696
GlobalSection(SolutionProperties) = preSolution
655697
HideSolutionNode = FALSE
@@ -677,5 +719,6 @@ Global
677719
{17D0F223-E156-42CF-ACA8-815733FE094F} = {9600C37E-439F-43D7-9D12-3AB0BEF7D906}
678720
{3DEB15F9-E7DA-403F-B6D3-A8499310397F} = {9600C37E-439F-43D7-9D12-3AB0BEF7D906}
679721
{0A5F920A-1BF5-4DAC-B799-0C618B203797} = {9600C37E-439F-43D7-9D12-3AB0BEF7D906}
722+
{26A3F930-FDAF-4886-9111-D326A6F68E82} = {9600C37E-439F-43D7-9D12-3AB0BEF7D906}
680723
EndGlobalSection
681724
EndGlobal

Sync.Test.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@
5555
<Exclude File="*.tt" />
5656
<Exclude File="*.ttinclude" />
5757
</Project>
58+
59+
<Project Name="MsgPack.UnitTest.Silverlight.5.FullTrust" Base="MsgPack.UnitTest.Silverlight.5">
60+
<Preserve Path="Properties\*" />
61+
<Preserve File="*.config" />
62+
<Preserve File="*.json" />
63+
<Preserve File="*.mpac" />
64+
<Preserve Path="App.xaml" />
65+
<Preserve Path="App.xaml.cs" />
66+
</Project>
5867

5968
<Project Name="MsgPack.UnitTest.Xamarin.iOS" Base="MsgPack.UnitTest">
6069
<Preserve Path="Properties\*" />

src/MsgPack/ReflectionAbstractions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,16 @@ public static bool GetIsPublic( this Type source )
134134
#endif // NETSTANDARD1_1 || NETSTANDARD1_3
135135
}
136136

137+
[System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Wrong detection" )]
138+
public static bool GetIsNestedPublic( this Type source )
139+
{
140+
#if NETSTANDARD1_1 || NETSTANDARD1_3
141+
return source.GetTypeInfo().IsNestedPublic;
142+
#else
143+
return source.IsNestedPublic;
144+
#endif // NETSTANDARD1_1 || NETSTANDARD1_3
145+
}
146+
137147
#if DEBUG
138148
public static bool GetIsPrimitive( this Type source )
139149
{

src/MsgPack/Serialization/ReflectionSerializers/ReflectionSerializerHelper.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ public static void GetMetadata(
237237
out DataMemberContract[] contracts,
238238
out MessagePackSerializer[] serializers )
239239
{
240+
SerializationTarget.VerifyCanSerializeTargetType( context, targetType );
241+
240242
getters = new Func<object, object>[ members.Count ];
241243
setters = new Action<object, object>[ members.Count ];
242244
memberInfos = new MemberInfo[ members.Count ];
@@ -259,15 +261,28 @@ public static void GetMetadata(
259261
FieldInfo asField;
260262
if ( ( asField = member.Member as FieldInfo ) != null )
261263
{
264+
if ( context.SerializerOptions.DisablePrivilegedAccess && !asField.GetIsPublic() )
265+
{
266+
continue;
267+
}
268+
262269
getters[ i ] = asField.GetValue;
263-
setters[ i ] = asField.SetValue;
270+
if ( !asField.IsInitOnly )
271+
{
272+
setters[ i ] = asField.SetValue;
273+
}
264274
}
265275
else
266276
{
267277
var property = member.Member as PropertyInfo;
268278
#if DEBUG
269279
Contract.Assert( property != null, "member.Member is PropertyInfo" );
270280
#endif // DEBUG
281+
if ( context.SerializerOptions.DisablePrivilegedAccess && !property.GetIsPublic() )
282+
{
283+
continue;
284+
}
285+
271286
var getter = property.GetGetMethod( true );
272287
if ( getter == null )
273288
{
@@ -276,7 +291,7 @@ public static void GetMetadata(
276291

277292
getters[ i ] = target => getter.InvokePreservingExceptionType( target, null );
278293
var setter = property.GetSetMethod( true );
279-
if ( setter != null )
294+
if ( setter != null && ( !context.SerializerOptions.DisablePrivilegedAccess || setter.GetIsPublic() ) )
280295
{
281296
setters[ i ] = ( target, value ) => setter.InvokePreservingExceptionType( target, new[] { value } );
282297
}

src/MsgPack/Serialization/SerializationTarget.cs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ internal class SerializationTarget
5656
private static readonly string MessagePackDeserializationConstructorAttributeTypeName = typeof( MessagePackDeserializationConstructorAttribute ).FullName;
5757
private static readonly string[] EmptyStrings = new string[ 0 ];
5858
private static readonly SerializingMember[] EmptyMembers = new SerializingMember[ 0 ];
59+
private static readonly Assembly ThisAssembly = typeof( SerializationTarget ).GetAssembly();
5960

6061
public IList<SerializingMember> Members { get; private set; }
6162

@@ -155,17 +156,27 @@ public static void VerifyType( Type targetType )
155156
}
156157
}
157158

159+
public static void VerifyCanSerializeTargetType( SerializationContext context, Type targetType )
160+
{
161+
if ( context.SerializerOptions.DisablePrivilegedAccess && !targetType.GetIsPublic() && !targetType.GetIsNestedPublic() && !ThisAssembly.Equals( targetType.GetAssembly() ) )
162+
{
163+
throw new SerializationException( String.Format( CultureInfo.CurrentCulture, "Cannot serialize type '{0}' because it is not public to the serializer.", targetType ) );
164+
}
165+
}
166+
158167
public static SerializationTarget Prepare( SerializationContext context, Type targetType )
159168
{
169+
VerifyCanSerializeTargetType( context, targetType );
170+
160171
var getters = GetTargetMembers( targetType ).OrderBy( entry => entry.Contract.Id ).ToArray();
161172

162173
if ( getters.Length == 0 )
163174
{
164175
throw new SerializationException( String.Format( CultureInfo.CurrentCulture, "Cannot serialize type '{0}' because it does not have any serializable fields nor properties.", targetType ) );
165176
}
166177

167-
var memberCandidates = getters.Where( entry => CheckTargetEligibility( entry.Member ) ).ToArray();
168178
bool? canDeserialize;
179+
var memberCandidates = getters.Where( entry => CheckTargetEligibility( context, entry.Member ) ).ToArray();
169180

170181
if ( memberCandidates.Length == 0 )
171182
{
@@ -495,7 +506,7 @@ private static SerializationException NewTypeCannotBeSerializedException( Type t
495506
}
496507

497508

498-
private static bool CheckTargetEligibility( MemberInfo member )
509+
private static bool CheckTargetEligibility( SerializationContext context, MemberInfo member )
499510
{
500511
var asProperty = member as PropertyInfo;
501512
var asField = member as FieldInfo;
@@ -510,12 +521,22 @@ private static bool CheckTargetEligibility( MemberInfo member )
510521
}
511522

512523
#if !NETSTANDARD1_1 && !NETSTANDARD1_3
513-
if ( asProperty.GetSetMethod( true ) != null )
524+
var setter = asProperty.GetSetMethod( true );
514525
#else
515-
if ( asProperty.SetMethod != null )
526+
var setter = asProperty.SetMethod;
516527
#endif // !NETSTANDARD1_1 && !NETSTANDARD1_3
528+
if ( setter != null )
517529
{
518-
return true;
530+
if ( setter.GetIsPublic() )
531+
{
532+
return true;
533+
}
534+
535+
if ( !context.SerializerOptions.DisablePrivilegedAccess )
536+
{
537+
// Can deserialize non-public setter if privileged.
538+
return true;
539+
}
519540
}
520541

521542
returnType = asProperty.PropertyType;

src/MsgPack/Serialization/SerializerOptions.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,47 @@ public bool DisableRuntimeCodeGeneration
137137
}
138138
#endif // !AOT
139139

140+
#if !FEATURE_CONCURRENT
141+
private volatile bool _isNonPublicAccessDisabled;
142+
#else
143+
private bool _isNonPublicAccessDisabled;
144+
#endif // !FEATURE_CONCURRENT
145+
146+
/// <summary>
147+
/// Gets or sets a value indicating whether generated and/or reflection serializers should not access non public members via privileged reflection.
148+
/// </summary>
149+
/// <value>
150+
/// <c>true</c> if privileged reflection access is disabled; otherwise, <c>false</c>. Defaults to <c>false</c>.
151+
/// </value>
152+
/// <remarks>
153+
/// The privileged reflection means:
154+
/// <list type="bullet">
155+
/// <item>Access for non-public fields or property accessors via reflection. This operation requires <c>ReflectionPermission</c> of <c>MemberAccess</c> or <c>RestrictedMemberAccess</c>.</item>
156+
/// <item>Writing values for init only fields via reflection. This operation requires <c>SecurityPermission</c> of <c>SerializationFormatter</c>.</item>
157+
/// </list>
158+
/// If the program run on non-privileged Silverlight environment or restricted desktop CLR,
159+
/// serialization and deserialization should fail with <c>SecurityException</c>.
160+
/// </remarks>
161+
public bool DisablePrivilegedAccess
162+
{
163+
get
164+
{
165+
#if !FEATURE_CONCURRENT
166+
return this._isNonPublicAccessDisabled;
167+
#else
168+
return Volatile.Read( ref this._isNonPublicAccessDisabled );
169+
#endif // !FEATURE_CONCURRENT
170+
}
171+
set
172+
{
173+
#if !FEATURE_CONCURRENT
174+
this._isNonPublicAccessDisabled = value;
175+
#else
176+
Volatile.Write( ref this._isNonPublicAccessDisabled, value );
177+
#endif // !FEATURE_CONCURRENT
178+
}
179+
}
180+
140181
#if FEATURE_TAP
141182

142183
private bool _withAsync;

0 commit comments

Comments
 (0)