summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/Runtime/Serialization/SafeSerializationManager.cs
blob: 585d36760567c31eac0e365bcf9ceb479c74195e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Security;

namespace System.Runtime.Serialization
{
    //
    // #SafeSerialization
    // 
    // Types which are serializable via the ISerializable interface have a problem when it comes to allowing
    // transparent subtypes which can allow themselves to serialize since the GetObjectData method is
    // SecurityCritical.
    // 
    // For instance, System.Exception implements ISerializable, however it is also desirable to have
    // transparent exceptions with their own fields that need to be serialized.  (For instance, in transparent
    // assemblies such as the DLR and F#, or even in partial trust application code).  Since overriding
    // GetObjectData requires that the overriding method be security critical, this won't work directly.
    //
    // SafeSerializationManager solves this problem by allowing any partial trust code to contribute
    // individual chunks of serializable data to be included in the serialized version of the derived class. 
    // These chunks are then deserialized back out of the serialized type and notified that they should
    // populate the fields of the deserialized object when serialization is complete.  This allows partial
    // trust or transparent code to participate in serialization of an ISerializable type without having to
    // override GetObjectData or implement the ISerializable constructor.
    //
    // On the serialization side, SafeSerializationManager has an event SerializeObjectState which it will
    // fire in response to serialization in order to gather the units of serializable data that should be
    // stored with the rest of the object during serialization.  Methods which respond to these events
    // create serializable objects which implement the ISafeSerializationData interface and add them to the
    // collection of other serialized data by calling AddSerializedState on the SafeSerializationEventArgs
    // passed into the event.
    // 
    // By using an event rather than a virtual method on the base ISerializable object, we allow multiple
    // potentially untrusted subclasses to participate in serialization, without each one having to ensure
    // that it calls up to the base type in order for the whole system to work.  (For instance Exception :
    // TrustedException : UntrustedException, in this scenario UntrustedException would be able to override
    // the virtual method an prevent TrustedException from ever seeing the method call, either accidentally
    // or maliciously).
    // 
    // Further, by only allowing additions of new chunks of serialization state rather than exposing the
    // whole underlying list, we avoid exposing potentially sensitive serialized state to any of the
    // potentially untrusted subclasses.
    // 
    // At deserialization time, SafeSerializationManager performs the reverse operation.  It deserializes the
    // chunks of serialized state, and then notifies them that the object they belong to is deserialized by
    // calling their CompleteSerialization method. In repsonse to this call, the state objects populate the
    // fields of the object being deserialized with the state that they held.
    // 
    // From a security perspective, the chunks of serialized state can only contain data that the specific
    // subclass itself had access to read (otherwise it wouldn't be able to populate the type with that
    // data), as opposed to having access to far more data in the SerializationInfo that GetObjectData uses.
    // Similarly, at deserialization time, the serialized state can only modify fields that the type itself
    // has access to (again, as opposed to the full SerializationInfo which could be modified).
    // 
    // Individual types which wish to participate in safe serialization do so by containing an instance of a
    // SafeSerializationManager and exposing its serialization event.  During GetObjectData, the
    // SafeSerializationManager is serialized just like any other field of the containing type.  However, at
    // the end of serialization it is called back one last time to CompleteSerialization.
    // 
    // In CompleteSerialization, if the SafeSerializationManager detects that it has extra chunks of
    // data to handle, it substitutes the root type being serialized (formerly the real type hosting the
    // SafeSerializationManager) with itself.  This allows it to gain more control over the deserialization
    // process.  It also saves away an extra bit of state in the serialization info indicating the real type
    // of object that should be recreated during deserialization.
    //
    // At this point the serialized state looks like this:
    //   Data:
    //     realSerializedData1
    //       ...
    //     realSerializedDataN
    //     safeSerializationData     -> this is the serialization data member of the parent type
    //       m_serializedState       -> list of saved serialized states from subclasses responding to the safe
    //                                  serialization event
    //     RealTypeSerializationName -> type which is using safe serialization
    //   Type:
    //     SafeSerializationManager
    //
    //  That is, the serialized data claims to be of type SafeSerializationManager, however contains only the
    //  data from the real object being serialized along with one bit of safe serialization metadata.
    //  
    //  At deserialization time, since the serialized data claims to be of type SafeSerializationManager, the
    //  root object being created is an instance of the SafeSerializationManager class.  However, it detects
    //  that this isn't a real SafeSerializationManager (by looking for the real type field in the metadata),
    //  and simply saves away the SerializationInfo and the real type being deserialized.
    //  
    //  Since SafeSerializationManager implements IObjectReference, the next step of deserialization is the
    //  GetRealObject callback.  This callback is the one responsible for getting the
    //  SafeSerializationManager out of the way and instead creating an instance of the actual type which was
    //  serialized.
    //  
    //  It does this by first creating an instance of the real type being deserialzed (saved away in the
    //  deserialzation constructor), but not running any of its constructors.  Instead, it walks the
    //  inheritance hierarchy (moving toward the most derived type) looking for the last full trust type to
    //  implement the standard ISerializable constructor before any type does not implement the constructor. 
    //  It is this last type's deserialization constructor which is then invoked, passing in the saved
    //  SerializationInfo.  Once the constructors are run, we return this object as the real deserialized
    //  object.
    //  
    //  The reason that we do this walk is so that ISerializable types can protect themselves from malicious
    //  input during deserialization by making their deserialization constructors unavailable to partial
    //  trust code.  By not requiring every type have a copy of this constructor, partial trust code can
    //  participate in safe serialization and not be required to have access to the parent's constructor. 
    //  
    //  It should be noted however, that this heuristic means that if a full trust type does derive from
    //  a transparent or partial trust type using this safe serialization mechanism, that full trust type
    //  will not have its constructor called. Further, the protection of not invoking partial trust
    //  deserialization constructors only comes into play if SafeSerializationManager is in control of
    //  deserialization, which means there must be at least one (even empty) safe serialization event
    //  handler registered.
    //  
    //  Another interesting note is that at this point there are now two SafeSerializationManagers alive for
    //  this deserialization.  The first object is the one which is controlling the deserialization and was
    //  created as the root object of the deserialization.  The second one is the object which contains the
    //  serialized data chunks and is a data member of the real object being deserialized.  For this reason,
    //  the data objects cannot be notified that the deserialization is complete during GetRealObject since
    //  the ISafeSerializationData objects are not members of the active SafeSerializationManager instance.
    //  
    //  The next step is the OnDeserialized callback, which comes to SafeSerializableObject since it was
    //  pretending to be the root object of the deserialization.  It responds to this callback by calling
    //  any existing OnDeserialized callback on the real type that was deserialized.
    //  
    //  The real type needs to call its data member SafeSerializationData object's CompleteDeserialization
    //  method in response to the OnDeserialized call.  This CompleteDeserialization call will then iterate
    //  through the ISafeSerializationData objects calling each of their CompleteDeserialization methods so
    //  that they can plug the nearly-complete object with their saved data.
    //  
    //  The reason for having a new ISafeSerializationData interface which is basically identical to
    //  IDeserializationCallback is that IDeserializationCallback will be called on the stored data chunks
    //  by the serialization code when they are deserialized, and that's not a desirable behavior. 
    //  Essentially, we need to change the meaning of the object parameter to mean "parent object which
    //  participated in safe serialization", rather than "this object".
    //  
    //  Implementing safe serialization on an ISerialiable type is relatively straight forward.  (For an
    //  example, see System.Exception):
    //  
    //    1. Include a data member of type SafeSerializationManager:
    //  
    //       private SafeSerializationManager m_safeSerializationManager;
    //     
    //    2. Add a protected SerializeObjectState event, which passes through to the SafeSerializationManager:
    //  
    //       protected event EventHandler<SafeSerializationEventArgs> SerializeObjectState
    //       {
    //           add { m_safeSerializationManager.SerializeObjectState += value; }
    //           remove { m_safeSerializationManager.SerializeObjectState -= value; }
    //       }
    //
    //    3. Serialize the safe serialization object in GetObjectData, and call its CompleteSerialization method:
    //  
    //       {
    //           info.AddValue("m_safeSerializationManager", m_safeSerializationManager, typeof(SafeSerializationManager));
    //           m_safeSerializationManager.CompleteSerialization(this, info, context);
    //       }
    //
    //    4. Add an OnDeserialized handler if one doesn't already exist, and call CompleteDeserialization in it:
    //  
    //       [OnDeserialized]
    //       private void OnDeserialized(StreamingContext context)
    //       {
    //           m_safeSerializationManager.CompleteDeserialization(this);
    //       }
    //
    // On the client side, using safe serialization is also pretty easy.  For example:
    // 
    //   [Serializable]
    //   public class TransparentException : Exception
    //   {
    //       [Serializable]
    //       private struct TransparentExceptionState : ISafeSerializationData
    //       {
    //           public string m_extraData;
    //
    //           void ISafeSerializationData.CompleteDeserialization(object obj)
    //           {
    //               TransparentException exception = obj as TransparentException;
    //               exception.m_state = this;
    //           }
    //       }
    //
    //       [NonSerialized]
    //       private TransparentExceptionState m_state = new TransparentExceptionState();
    //
    //       public TransparentException()
    //       {
    //           SerializeObjectState += delegate(object exception, SafeSerializationEventArgs eventArgs)
    //           {
    //               eventArgs.AddSerializedState(m_state);
    //           };
    //       }
    //
    //       public string ExtraData
    //       {
    //           get { return m_state.m_extraData; }
    //           set { m_state.m_extraData = value; }
    //       }
    //   }
    // 

    // SafeSerializationEventArgs are provided to the delegates which do safe serialization.  Each delegate
    // serializes its own state into an IDeserializationCallback instance which must, itself, be serializable.
    // These indivdiual states are then added to the SafeSerializationEventArgs in order to be saved away when
    // the original ISerializable type is serialized.
    public sealed class SafeSerializationEventArgs : EventArgs
    {
        private StreamingContext m_streamingContext;
        private List<object> m_serializedStates = new List<object>();

        internal SafeSerializationEventArgs(StreamingContext streamingContext)
        {
            m_streamingContext = streamingContext;
        }

        public void AddSerializedState(ISafeSerializationData serializedState)
        {
            if (serializedState == null)
                throw new ArgumentNullException(nameof(serializedState));
            if (!serializedState.GetType().IsSerializable)
                throw new ArgumentException(Environment.GetResourceString("Serialization_NonSerType", serializedState.GetType(), serializedState.GetType().Assembly.FullName));

            m_serializedStates.Add(serializedState);
        }

        internal IList<object> SerializedStates
        {
            get { return m_serializedStates; }
        }

        public StreamingContext StreamingContext
        {
            get { return m_streamingContext; }
        }
    }

    // Interface to be supported by objects which are stored in safe serialization stores
    public interface ISafeSerializationData
    {
        // CompleteDeserialization is called when the object to which the extra serialized data was attached
        // has completed its deserialization, and now needs to be populated with the extra data stored in
        // this object.
        void CompleteDeserialization(object deserialized);
    }
#if FEATURE_SERIALIZATION
    // Helper class to implement safe serialization.  Concrete ISerializable types which want to allow
    // transparent subclasses code to participate in serialization should contain an instance of 
    // SafeSerializationManager and wire up to it as described in code:#SafeSerialization.
    [Serializable]
    internal sealed class SafeSerializationManager : IObjectReference, ISerializable
    {
        // Saved states to store in the serialization stream.  This is typed as object rather than
        // ISafeSerializationData because ISafeSerializationData can't be marked serializable.
        private IList<object> m_serializedStates;

        // This is the SerializationInfo that is used when the SafeSerializationManager type has replaced
        // itself as the target of serialziation.  It is not used directly by the safe serialization code,
        // but just held onto so that the real object being deserialzed can use it later.
        private SerializationInfo m_savedSerializationInfo;

        // Real object that we've deserialized - this is stored when we complete construction and calling
        // the deserialization .ctors on it and is used when we need to notify the stored safe
        // deserialization data that they should populate the object with their fields.
        private object m_realObject;

        // Real type that should be deserialized
        private RuntimeType m_realType;
        
        // Event fired when we need to collect state to serialize into the parent object
        internal event EventHandler<SafeSerializationEventArgs> SerializeObjectState;

        // Name that is used to store the real type being deserialized in the main SerializationInfo
        private const string RealTypeSerializationName = "CLR_SafeSerializationManager_RealType";

        internal SafeSerializationManager()
        {
        }

        private SafeSerializationManager(SerializationInfo info, StreamingContext context)
        {
            // We need to determine if we're being called to really deserialize a SafeSerializationManager,
            // or if we're being called because we've intercepted the deserialization callback for the real
            // object being deserialized.  We use the presence of the RealTypeSerializationName field in the
            // serialization info to indicate that this is the interception callback and we just need to
            // safe the info.  If that field is not present, then we should be in a real deserialization
            // construction.
            RuntimeType realType = info.GetValueNoThrow(RealTypeSerializationName, typeof(RuntimeType)) as RuntimeType;

            if (realType == null)
            {
                m_serializedStates = info.GetValue("m_serializedStates", typeof(List<object>)) as List<object>;
            }
            else
            {
                m_realType = realType;
                m_savedSerializationInfo = info;
            }
        }

        // Determine if the serialization manager is in an active state - that is if any code is hooked up
        // to use it for serialization
        internal bool IsActive
        {
            get { return SerializeObjectState != null; }
        }

        // CompleteSerialization is called by the base ISerializable in its GetObjectData method.  It is
        // responsible for gathering up the serialized object state of any delegates that wish to add their
        // own state to the serialized object.
        internal void CompleteSerialization(object serializedObject,
                                            SerializationInfo info,
                                            StreamingContext context)
        {
            Contract.Requires(serializedObject != null);
            Contract.Requires(info != null);
            Contract.Requires(typeof(ISerializable).IsAssignableFrom(serializedObject.GetType()));
            Contract.Requires(serializedObject.GetType().IsAssignableFrom(info.ObjectType));

            // Clear out any stale state
            m_serializedStates = null;

            // We only want to kick in our special serialization sauce if someone wants to participate in
            // it, otherwise if we have no delegates registered there's no reason for us to get in the way
            // of the regular serialization machinery.
            EventHandler<SafeSerializationEventArgs> serializeObjectStateEvent = SerializeObjectState;
            if (serializeObjectStateEvent != null)
            {
                // Get any extra data to add to our serialization state now
                SafeSerializationEventArgs eventArgs = new SafeSerializationEventArgs(context);
                serializeObjectStateEvent(serializedObject, eventArgs);
                m_serializedStates = eventArgs.SerializedStates;

                // Replace the type to be deserialized by the standard serialization code paths with
                // ourselves, which allows us to control the deserialization process.
                info.AddValue(RealTypeSerializationName, serializedObject.GetType(), typeof(RuntimeType));
                info.SetType(typeof(SafeSerializationManager));
            } 
        }

        // CompleteDeserialization is called by the base ISerializable object's OnDeserialized handler to
        // finish the deserialization of the object by notifying the saved states that they should
        // re-populate their portions of the deserialized object.
        internal void CompleteDeserialization(object deserializedObject)
        {
            Contract.Requires(deserializedObject != null);

            if (m_serializedStates != null)
            {
                foreach (ISafeSerializationData serializedState in m_serializedStates)
                {
                    serializedState.CompleteDeserialization(deserializedObject);
                }
            }
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("m_serializedStates", m_serializedStates, typeof(List<IDeserializationCallback>));
        }

        // GetRealObject intercepts the deserialization process in order to allow deserializing part of the
        // object's inheritance heirarchy using standard ISerializable constructors, and the remaining
        // portion using the saved serialization states.
        object IObjectReference.GetRealObject(StreamingContext context)
        {
            // If we've already deserialized the real object, use that rather than deserializing it again
            if (m_realObject != null)
            {
                return m_realObject;
            }

            // If we don't have a real type to deserialize, then this is really a SafeSerializationManager
            // and we don't need to rebuild the object that we're standing in for.
            if (m_realType == null)
            {
                return this;
            }

            // Look for the last type in GetRealType's inheritance hierarchy which implements a critical
            // deserialization constructor.  This will be the object that we use as the deserialization
            // construction type to initialize via standard ISerializable semantics

            // First build up the chain starting at the type below Object and working to the real type we
            // serialized.
            Stack inheritanceChain = new Stack();
            RuntimeType currentType = m_realType;
            do
            {
                inheritanceChain.Push(currentType);
                currentType = currentType.BaseType as RuntimeType;
            }
            while (currentType != typeof(object));

            // Now look for the first type that does not implement the ISerializable .ctor.  When we find
            // that, previousType will point at the last type that did implement the .ctor.  We require that
            // the .ctor we invoke also be non-transparent
            RuntimeConstructorInfo serializationCtor = null;
            RuntimeType previousType = null;
            do
            {
                previousType = currentType;
                currentType = inheritanceChain.Pop() as RuntimeType;
                serializationCtor = currentType.GetSerializationCtor();
            }
            while (serializationCtor != null && serializationCtor.IsSecurityCritical);
            
            // previousType is the last type that did implement the deserialization .ctor before the first
            // type that did not, so we'll grab it's .ctor to use for deserialization.
            BCLDebug.Assert(previousType != null, "We should have at least one inheritance from the base type");
            serializationCtor = ObjectManager.GetConstructor(previousType);

            // Allocate an instance of the final type and run the selected .ctor on that instance to get the
            // standard ISerializable initialization done.
            object deserialized = FormatterServices.GetUninitializedObject(m_realType);
            serializationCtor.SerializationInvoke(deserialized, m_savedSerializationInfo, context);
            m_savedSerializationInfo = null;
            m_realType = null;

            // Save away the real object that was deserialized so that we can fill it in later, and return
            // it back as the object that should result from the final deserialization.
            m_realObject = deserialized;
            return deserialized;
        }

        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            // We only need to complete deserialization if we were hooking the deserialization process.  If
            // we have not deserialized an object in the GetRealObject call, then there's nothing more for
            // us to do here.
            if (m_realObject != null)
            {
                // Fire the real object's OnDeserialized method if they registered one.  Since we replaced
                // ourselves as the target of the deserialization, OnDeserialized on the target won't
                // automatically get triggered unless we do it manually.
                SerializationEvents cache = SerializationEventsCache.GetSerializationEventsForType(m_realObject.GetType());
                cache.InvokeOnDeserialized(m_realObject, context);
                m_realObject = null;
            }
        }
    }
#endif
}