QueryLanguage.Remoting.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT License.
  3. // See the LICENSE file in the project root for more information.
  4. // TODO: does this belong in here or System.Reactive.Net?
  5. // We might be able to completely remove the net472 target from System.Reactive.Net, in which case this would have
  6. // to come out, but that does raise the question of where the thing should live. Back in System.Reactive.Runtime.Remoting?
  7. // Or does that risk creating circular type forwarders if versions get out of alignment?
  8. // We don't really want it to remain in System.Reactive since we're trying to deprecate this component.
  9. #if HAS_REMOTING
  10. extern alias SystemReactiveNet;
  11. using SystemReactiveNet::System.Reactive.Disposables;
  12. using SystemReactiveNet::System.Reactive;
  13. using System.Runtime.Remoting;
  14. using System.Runtime.Remoting.Lifetime;
  15. using System.Security;
  16. using System.Threading;
  17. //
  18. // DESIGN: The MarshalByRefObject (MBRO) implementations for RemotableObserver and RemotableSubscription act as
  19. // self-sponsoring objects controlling their lease times in order to tie those to the lifetime of the
  20. // underlying observable sequence (ended by OnError or OnCompleted) or the user-controlled subscription
  21. // lifetime. If we were to implement InitializeLifetimeService to return null, we'd end up with leases
  22. // that are infinite, so we need a more fine-grained lease scheme. The default configuration would time
  23. // out after 5 minutes, causing clients to fail while they're still observing the sequence. To solve
  24. // this, those MBROs also implement ISponsor with a Renewal method that continues to renew the lease
  25. // upon every call. When the sequence comes to an end or the subscription is disposed, the sponsor gets
  26. // unregistered, allowing the objects to be reclaimed eventually by the Remoting infrastructure.
  27. //
  28. // SECURITY: Registration and unregistration of sponsors is protected by SecurityCritical annotations. The
  29. // implementation of ISponsor is known (i.e. no foreign implementation can be passed in) at the call
  30. // sites of the Register and Unregister methods. The call to Register happens in the SecurityCritical
  31. // InitializeLifetimeService method and is called by trusted Remoting infrastructure. The Renewal
  32. // method is also marked as SecurityCritical and called by Remoting. The Unregister method is wrapped
  33. // in a ***SecurityTreatAsSafe*** private method which only gets called by the observer's OnError and
  34. // OnCompleted notifications, or the subscription's Dispose method. In the former case, the sequence
  35. // indicates it has reached the end, and hence resources can be reclaimed. Clients will no longer be
  36. // connected to the source due to auto-detach behavior enforced in the SerializableObservable client-
  37. // side implementation. In the latter case of disposing the subscription, the client is in control
  38. // and will cause the underlying remote subscription to be disposed as well, allowing resources to be
  39. // reclaimed. Rogue messages on either the data or the subscription channel can cause a DoS of the
  40. // client-server communication but this is subject to the security of the Remoting channels used. In
  41. // no case an untrusted party can cause _extension_ of the lease time.
  42. //
  43. //
  44. // Notice this assembly is marked as APTCA in official builds, causing methods to be treated as transparent,
  45. // thus requiring the ***SecurityTreatAsSafe*** annotation on the security boundaries described above. When not
  46. // applied, the following exception would occur at runtime:
  47. //
  48. // System.MethodAccessException:
  49. //
  50. // Attempt by security transparent method 'System.Reactive.Linq.QueryLanguage+RemotableObservable`1+
  51. // RemotableSubscription<T>.Unregister()' to access security critical method 'System.Runtime.Remoting.Lifetime.
  52. // ILease.Unregister(System.Runtime.Remoting.Lifetime.ISponsor)' failed.
  53. //
  54. // Assembly 'System.Reactive.Linq, Version=2.0.ymmdd.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
  55. // is marked with the AllowPartiallyTrustedCallersAttribute, and uses the level 2 security transparency model.
  56. // Level 2 transparency causes all methods in AllowPartiallyTrustedCallers assemblies to become security
  57. // transparent by default, which may be the cause of this exception.
  58. //
  59. //
  60. // The two CodeAnalysis suppressions below are explained by the Justification property (scroll to the right):
  61. //
  62. [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.")]
  63. [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.")]
  64. namespace System.Reactive.Linq
  65. {
  66. public static partial class RemotingObservable
  67. {
  68. #region Remotable
  69. private static IObservable<TSource> Remotable_<TSource>(IObservable<TSource> source)
  70. {
  71. return new SerializableObservable<TSource>(new RemotableObservable<TSource>(source, null));
  72. }
  73. private static IObservable<TSource> Remotable_<TSource>(IObservable<TSource> source, ILease? lease)
  74. {
  75. return new SerializableObservable<TSource>(new RemotableObservable<TSource>(source, lease));
  76. }
  77. [Serializable]
  78. private sealed class SerializableObservable<T> : IObservable<T>
  79. {
  80. private readonly RemotableObservable<T> _remotableObservable;
  81. public SerializableObservable(RemotableObservable<T> remotableObservable)
  82. {
  83. _remotableObservable = remotableObservable;
  84. }
  85. public IDisposable Subscribe(IObserver<T> observer)
  86. {
  87. var consumer = SafeObserver<T>.Wrap(observer);
  88. //
  89. // [OK] Use of unsafe Subscribe: non-pretentious transparent wrapping through remoting; exception coming from the remote object is not re-routed.
  90. //
  91. var d = _remotableObservable.Subscribe/*Unsafe*/(new RemotableObserver<T>(consumer));
  92. consumer.SetResource(d);
  93. return d;
  94. }
  95. }
  96. private sealed class RemotableObserver<T> : MarshalByRefObject, IObserver<T>, ISponsor
  97. {
  98. private readonly IObserver<T> _underlyingObserver;
  99. public RemotableObserver(IObserver<T> underlyingObserver)
  100. {
  101. _underlyingObserver = underlyingObserver;
  102. }
  103. public void OnNext(T value)
  104. {
  105. _underlyingObserver.OnNext(value);
  106. }
  107. public void OnError(Exception exception)
  108. {
  109. try
  110. {
  111. _underlyingObserver.OnError(exception);
  112. }
  113. finally
  114. {
  115. Unregister();
  116. }
  117. }
  118. public void OnCompleted()
  119. {
  120. try
  121. {
  122. _underlyingObserver.OnCompleted();
  123. }
  124. finally
  125. {
  126. Unregister();
  127. }
  128. }
  129. [SecuritySafeCritical] // See remarks at the top of the file.
  130. private void Unregister()
  131. {
  132. var lease = (ILease)RemotingServices.GetLifetimeService(this);
  133. lease?.Unregister(this);
  134. }
  135. [SecurityCritical]
  136. public override object InitializeLifetimeService()
  137. {
  138. var lease = (ILease)base.InitializeLifetimeService();
  139. lease.Register(this);
  140. return lease;
  141. }
  142. [SecurityCritical]
  143. TimeSpan ISponsor.Renewal(ILease lease)
  144. {
  145. return lease.InitialLeaseTime;
  146. }
  147. }
  148. [Serializable]
  149. private sealed class RemotableObservable<T> : MarshalByRefObject, IObservable<T>
  150. {
  151. private readonly IObservable<T> _underlyingObservable;
  152. private readonly ILease? _lease;
  153. public RemotableObservable(IObservable<T> underlyingObservable, ILease? lease)
  154. {
  155. _underlyingObservable = underlyingObservable;
  156. _lease = lease;
  157. }
  158. public IDisposable Subscribe(IObserver<T> observer)
  159. {
  160. //
  161. // [OK] Use of unsafe Subscribe: non-pretentious transparent wrapping through remoting; throwing across remoting boundaries is fine.
  162. //
  163. return new RemotableSubscription(_underlyingObservable.Subscribe/*Unsafe*/(observer));
  164. }
  165. [SecurityCritical]
  166. public override object? InitializeLifetimeService()
  167. {
  168. return _lease;
  169. }
  170. private sealed class RemotableSubscription : MarshalByRefObject, IDisposable, ISponsor
  171. {
  172. private IDisposable _underlyingSubscription;
  173. public RemotableSubscription(IDisposable underlyingSubscription)
  174. {
  175. _underlyingSubscription = underlyingSubscription;
  176. }
  177. public void Dispose()
  178. {
  179. //
  180. // Avoiding double-dispose and dropping the reference upon disposal.
  181. //
  182. using (Interlocked.Exchange(ref _underlyingSubscription, Disposable.Empty))
  183. {
  184. Unregister();
  185. }
  186. }
  187. [SecuritySafeCritical] // See remarks at the top of the file.
  188. private void Unregister()
  189. {
  190. var lease = (ILease)RemotingServices.GetLifetimeService(this);
  191. lease?.Unregister(this);
  192. }
  193. [SecurityCritical]
  194. public override object InitializeLifetimeService()
  195. {
  196. var lease = (ILease)base.InitializeLifetimeService();
  197. lease.Register(this);
  198. return lease;
  199. }
  200. [SecurityCritical]
  201. TimeSpan ISponsor.Renewal(ILease lease)
  202. {
  203. return lease.InitialLeaseTime;
  204. }
  205. }
  206. }
  207. #endregion
  208. }
  209. }
  210. #endif