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
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
#if !NO_REMOTING
using System.Reactive.Disposables;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Lifetime;
using System.Security;
using System.Threading;
//
// DESIGN: The MarshalByRefObject (MBRO) implementations for RemotableObserver and RemotableSubscription act as
// self-sponsoring objects controlling their lease times in order to tie those to the lifetime of the
// underlying observable sequence (ended by OnError or OnCompleted) or the user-controlled subscription
// lifetime. If we were to implement InitializeLifetimeService to return null, we'd end up with leases
// that are infinite, so we need a more fine-grained lease scheme. The default configuration would time
// out after 5 minutes, causing clients to fail while they're still observing the sequence. To solve
// this, those MBROs also implement ISponsor with a Renewal method that continues to renew the lease
// upon every call. When the sequence comes to an end or the subscription is disposed, the sponsor gets
// unregistered, allowing the objects to be reclaimed eventually by the Remoting infrastructure.
//
// SECURITY: Registration and unregistration of sponsors is protected by SecurityCritical annotations. The
// implementation of ISponsor is known (i.e. no foreign implementation can be passed in) at the call
// sites of the Register and Unregister methods. The call to Register happens in the SecurityCritical
// InitializeLifetimeService method and is called by trusted Remoting infrastructure. The Renewal
// method is also marked as SecurityCritical and called by Remoting. The Unregister method is wrapped
// in a ***SecurityTreatAsSafe*** private method which only gets called by the observer's OnError and
// OnCompleted notifications, or the subscription's Dispose method. In the former case, the sequence
// indicates it has reached the end, and hence resources can be reclaimed. Clients will no longer be
// connected to the source due to auto-detach behavior enforced in the SerializableObservable client-
// side implementation. In the latter case of disposing the subscription, the client is in control
// and will cause the underlying remote subscription to be disposed as well, allowing resources to be
// reclaimed. Rogue messages on either the data or the subscription channel can cause a DoS of the
// client-server communication but this is subject to the security of the Remoting channels used. In
// no case an untrusted party can cause _extension_ of the lease time.
//
//
// Notice this assembly is marked as APTCA in official builds, causing methods to be treated as transparent,
// thus requiring the ***SecurityTreatAsSafe*** annotation on the security boundaries described above. When not
// applied, the following exception would occur at runtime:
//
// System.MethodAccessException:
//
// Attempt by security transparent method 'System.Reactive.Linq.QueryLanguage+RemotableObservable`1+
// RemotableSubscription<T>.Unregister()' to access security critical method 'System.Runtime.Remoting.Lifetime.
// ILease.Unregister(System.Runtime.Remoting.Lifetime.ISponsor)' failed.
//
// Assembly 'System.Reactive.Linq, Version=2.0.ymmdd.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
// is marked with the AllowPartiallyTrustedCallersAttribute, and uses the level 2 security transparency model.
// Level 2 transparency causes all methods in AllowPartiallyTrustedCallers assemblies to become security
// transparent by default, which may be the cause of this exception.
//
//
// The two CodeAnalysis suppressions below are explained by the Justification property (scroll to the right):
//
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2136:TransparencyAnnotationsShouldNotConflictFxCopRule", Scope = "member", Target = "System.Reactive.Linq.QueryLanguage+RemotableObserver`1.#Unregister()", Justification = "This error only occurs while running FxCop on local builds that don't have NO_CODECOVERAGE set, causing the assembly not to be marked with APTCA (see AssemblyInfo.cs). When APTCA is enabled in official builds, this SecurityTreatAsSafe annotation is required.")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2136:TransparencyAnnotationsShouldNotConflictFxCopRule", Scope = "member", Target = "System.Reactive.Linq.QueryLanguage+RemotableObservable`1+RemotableSubscription.#Unregister()", Justification = "This error only occurs while running FxCop on local builds that don't have NO_CODECOVERAGE set, causing the assembly not to be marked with APTCA (see AssemblyInfo.cs). When APTCA is enabled in official builds, this SecurityTreatAsSafe annotation is required.")]
namespace System.Reactive.Linq
{
public static partial class RemotingObservable
{
#region Remotable
private static IObservable<TSource> Remotable_<TSource>(IObservable<TSource> source)
{
return new SerializableObservable<TSource>(new RemotableObservable<TSource>(source, null));
}
private static IObservable<TSource> Remotable_<TSource>(IObservable<TSource> source, ILease lease)
{
return new SerializableObservable<TSource>(new RemotableObservable<TSource>(source, lease));
}
[Serializable]
class SerializableObservable<T> : IObservable<T>
{
readonly RemotableObservable<T> remotableObservable;
public SerializableObservable(RemotableObservable<T> remotableObservable)
{
this.remotableObservable = remotableObservable;
}
public IDisposable Subscribe(IObserver<T> observer)
{
var d = new SingleAssignmentDisposable();
var o = Observer.Create<T>(
observer.OnNext,
ex =>
{
//
// Make call to the remote subscription, causing lease renewal to be stopped.
//
using (d)
{
observer.OnError(ex);
}
},
() =>
{
//
// Make call to the remote subscription, causing lease renewal to be stopped.
//
using (d)
{
observer.OnCompleted();
}
}
);
//
// [OK] Use of unsafe Subscribe: non-pretentious transparent wrapping through remoting; exception coming from the remote object is not re-routed.
//
d.Disposable = remotableObservable.Subscribe/*Unsafe*/(new RemotableObserver<T>(o));
return d;
}
}
class RemotableObserver<T> : MarshalByRefObject, IObserver<T>, ISponsor
{
readonly IObserver<T> underlyingObserver;
public RemotableObserver(IObserver<T> underlyingObserver)
{
this.underlyingObserver = underlyingObserver;
}
public void OnNext(T value)
{
underlyingObserver.OnNext(value);
}
public void OnError(Exception exception)
{
try
{
underlyingObserver.OnError(exception);
}
finally
{
Unregister();
}
}
public void OnCompleted()
{
try
{
underlyingObserver.OnCompleted();
}
finally
{
Unregister();
}
}
[SecuritySafeCritical] // See remarks at the top of the file.
private void Unregister()
{
var lease = (ILease)RemotingServices.GetLifetimeService(this);
if (lease != null)
lease.Unregister(this);
}
[SecurityCritical]
public override object InitializeLifetimeService()
{
var lease = (ILease)base.InitializeLifetimeService();
lease.Register(this);
return lease;
}
[SecurityCritical]
TimeSpan ISponsor.Renewal(ILease lease)
{
return lease.InitialLeaseTime;
}
}
[Serializable]
sealed class RemotableObservable<T> : MarshalByRefObject, IObservable<T>
{
readonly IObservable<T> underlyingObservable;
readonly ILease lease;
public RemotableObservable(IObservable<T> underlyingObservable, ILease lease)
{
this.underlyingObservable = underlyingObservable;
this.lease = lease;
}
public IDisposable Subscribe(IObserver<T> observer)
{
//
// [OK] Use of unsafe Subscribe: non-pretentious transparent wrapping through remoting; throwing across remoting boundaries is fine.
//
return new RemotableSubscription(underlyingObservable.Subscribe/*Unsafe*/(observer));
}
[SecurityCritical]
public override object InitializeLifetimeService()
{
return lease;
}
sealed class RemotableSubscription : MarshalByRefObject, IDisposable, ISponsor
{
private IDisposable underlyingSubscription;
public RemotableSubscription(IDisposable underlyingSubscription)
{
this.underlyingSubscription = underlyingSubscription;
}
public void Dispose()
{
//
// Avoiding double-dispose and dropping the reference upon disposal.
//
using (Interlocked.Exchange(ref underlyingSubscription, Disposable.Empty))
{
Unregister();
}
}
[SecuritySafeCritical] // See remarks at the top of the file.
private void Unregister()
{
var lease = (ILease)RemotingServices.GetLifetimeService(this);
if (lease != null)
lease.Unregister(this);
}
[SecurityCritical]
public override object InitializeLifetimeService()
{
var lease = (ILease)base.InitializeLifetimeService();
lease.Register(this);
return lease;
}
[SecurityCritical]
TimeSpan ISponsor.Renewal(ILease lease)
{
return lease.InitialLeaseTime;
}
}
}
#endregion
}
}
#endif
|