Re: Dispose must be thread-safe ?

From: Valery Pryamikov (Valery_at_nospam.harper.no)
Date: 03/05/04


Date: Fri, 5 Mar 2004 09:56:03 +0100

To make myself even more clear:

protected void Dispose(bool fDisposing) {
if (!this._disposed) { //#1
this._disposed = true; //#2
IntPtr _handle = this._unmanagedResourceToDispose; //#3
UnmanagedHelper.CloseHandle(_handle); //#4
} }

If Dispose was called from program -> the object was reachable at the start
of Dispose call. Lines #1, #2 and #3 access 'this' pointer and therefore
keep object non-Collectable. Object could become collectable somewhere at
the middle of (#3) (after complete read memory access from
this._unmanagedResourceToDispose), but at that time this._disposed will be
already set to true (#2) and any attempt to reenter that code from Finalizer
thread will be blocked by simple reentry protection if (!this._disposed)
(#1).
You don't need to write long response, just say if it possible or not for
object to be collected before it becomes unreacable...
If there is a guarantee of that object will be collected only after it
becomes unreacable (my point) - then thread-safety is not required and
reentry protection of Dispose is indeed enough and it does not matter if
call to Dispose was queued into thread pool or object was resurrected from
Finalize, but this simple reentry protection guaranteed to work in all cases
(except for calling Dispose on the Target of WeakReference with resurrection
tracking).

BTW: just in addition to you argument about malicious person queueing
dispose call - race conditions generally are not exploitable (AFAIK - there
is no known exploit of the race condition).

Best regards,
-Valery.

"Chris Brumme" <cbrumme@microsoft.com> wrote in message
news:yfwydRfAEHA.2804@cpmsftngxa06.phx.gbl...
> There may be some confusion here about resurrection. It's true that
> WeakReference and GCHandle give you the ability to create weak handles
that
> are either "short" or "long" weak handles. By short, I mean that the
> handle is zeroed when the target object is first discovered to be garbage.
> (As you know from my blog post on Finalization, this is the point at which
> objects are moved from the RegisteredForFinalization queue to the
> ReadyForFinalization queue, and the entire reachable graph from each such
> finalizable object is promoted). By long, I mean that the handle is not
> zeroed until after finalization has proceeded and the target of the handle
> is actually collected in some subsequent GC. In the APIs, we refer to
long
> weak handles as 'trackResurrection' because they track the target object
> even as it is resurrected by the movement from the
> RegisteredForFinalization queue to the ReadyForFinalization queue and the
> subsequent promotion.
>
> Unfortunately, that use of the term resurrection is a bit different from
> what I refer to in my blog post on Finalization -- although it's certainly
> related to the underlying phenomenon of "bringing an object back from the
> dead" by promoting it after it was determined to be garbage. The
> resurrection case I'm talking about in the blog is more general. Let's
say
> there are two finalizable objects 'a' and 'b' of type A and B
respectively.
> You wrote type B and you wrote your Finalize method without any regard
for
> free-threading. Now I'm going to come along and create a multi-threaded
> race condition in your Finalize method.
>
> An easy way for me to do this is to create my 'a' object with a reference
> to an instance of your type 'b'. If I create 'a' and 'b' at the same
time,
> they are extremely likely to be in the same generation. If I release both
> objects after creating them, they are very likely to be collected
together,
> on the next GC. At that time, they will both be moved to the
> ReadyForFinalization queue and will be promoted. Now the finalizer thread
> starts calling Finalize methods. Let's say there is a 50% chance of 'a's
> Finalize being called before 'b's Finalize method.
>
> Inside my Finalize method, all I need to do is place 'a' into a static
> field. This means that 'a' and 'b' are both reachable. Neither one is
> going to be collected. Your object has just been resurrected. You had no
> choice in the matter. However, your object is also on the
ReadyForFinalize
> queue. This means that the finalizer thread is going to call your
Finalize
> method as it works its way through that queue. For the example we
> discussed, there's some likelihood that 'b's Finalize method will be
called
> as soon as I return from my 'a's Finalize method.
>
> If I'm vicious, I can do a QueueUserWorkItem of a delegate that will call
> the IDisposable::Dispose method of your 'b' instance. (Indeed, I don't
> need to put your object into a static field, since the QueueUserWorkItem
of
> my delegate will keep 'b' alive until the ThreadPool has called Dispose on
> it.
>
> At this point, we have the finalizer thread about to call your Finalize
> method. And we have another thread from the ThreadPool about to call your
> Dispose method. The result is that you will receive one
> Dispose(disposing=false) and one Dispose(disposing=true) call in a
> free-threaded race on your object. If people can obtain references to
your
> object, they can subject you to this free-threaded abuse.
>
> Do you need to worry about it? Well, if you are running in partial trust
> and maintaining your own cache, the worst that can happen is that you get
> confused. If someone subjects you to this sort of free-threaded abuse,
you
> could say that it's their bug.
>
> But if you are running with full trust and you are managing a protected
> resource, then the implementer of A might be a malicious hacker who is
> trying to create a handle recycling attack.
>



Relevant Pages

  • Re: Replacement for runFinalizersOnExit()
    ... as to set a timer and call dispose() yourself if the user ... hasn't called it within T milliseconds. ... will be much protection against a determined snoop. ... finalize() immediately puts you into a concurrent-programming situation. ...
    (comp.lang.java.programmer)
  • Dispose and Finalize hell!
    ... public static myClass Instance ... I think B should implement only Dispose so that C can free ... I don't know why B should implement a Finalize method: ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: Dispose and Finalize hell!
    ... It might do nothing useful for now, but maybe later the class grows to manage resources that are worth of eliminating. ... If all callers dispose you, you have more freedom to modify the class. ... I don't know why B should implement a Finalize method: if C doesn't remember to call objB.Dispose GC will call objA.Finalize and I don't see any benefit in implementing Finalize in B because it will be called in a non deterministic way as for objA.Finalize! ...
    (microsoft.public.dotnet.languages.csharp)
  • RE: GC.Collect doesnt trigger finalizers?
    ... not have their Finalize method called. ... In addition, by default, Finalize methods are not ... >> It might have something to do with the way Garbage Collection works. ... >>> Public Sub New ...
    (microsoft.public.dotnet.framework)
  • Re: Automatic Dispose
    ... But, a SqlConnection itselft is a managed resource and, as such, should not ... instance user uses "Using" and Dispose is called automatically). ... Public Overridable Sub Dispose() Implements IDisposable.Dispose ... Protected Overrides Sub Finalize() ...
    (microsoft.public.dotnet.languages.vb)

Loading