123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- // 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.
- // TODO: does this belong in here or System.Reactive.Net?
- // We might be able to completely remove the net472 target from System.Reactive.Net, in which case this would have
- // to come out, but that does raise the question of where the thing should live. Back in System.Reactive.Runtime.Remoting?
- // Or does that risk creating circular type forwarders if versions get out of alignment?
- // We don't really want it to remain in System.Reactive since we're trying to deprecate this component.
- #if HAS_REMOTING
- extern alias SystemReactiveNet;
- using SystemReactiveNet::System.Reactive.Disposables;
- using SystemReactiveNet::System.Reactive;
- 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 = "~M:System.Reactive.Linq.RemotingObservable.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 = "~M:System.Reactive.Linq.RemotingObservable.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]
- private sealed class SerializableObservable<T> : IObservable<T>
- {
- private readonly RemotableObservable<T> _remotableObservable;
- public SerializableObservable(RemotableObservable<T> remotableObservable)
- {
- _remotableObservable = remotableObservable;
- }
- public IDisposable Subscribe(IObserver<T> observer)
- {
- var consumer = SafeObserver<T>.Wrap(observer);
- //
- // [OK] Use of unsafe Subscribe: non-pretentious transparent wrapping through remoting; exception coming from the remote object is not re-routed.
- //
- var d = _remotableObservable.Subscribe/*Unsafe*/(new RemotableObserver<T>(consumer));
- consumer.SetResource(d);
- return d;
- }
- }
- private sealed class RemotableObserver<T> : MarshalByRefObject, IObserver<T>, ISponsor
- {
- private readonly IObserver<T> _underlyingObserver;
- public RemotableObserver(IObserver<T> underlyingObserver)
- {
- _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);
- 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]
- private sealed class RemotableObservable<T> : MarshalByRefObject, IObservable<T>
- {
- private readonly IObservable<T> _underlyingObservable;
- private readonly ILease? _lease;
- public RemotableObservable(IObservable<T> underlyingObservable, ILease? lease)
- {
- _underlyingObservable = underlyingObservable;
- _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;
- }
- private sealed class RemotableSubscription : MarshalByRefObject, IDisposable, ISponsor
- {
- private IDisposable _underlyingSubscription;
- public RemotableSubscription(IDisposable underlyingSubscription)
- {
- _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);
- 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
|