Skip to content

Fix watson buckets setting race condition#46636

Merged
janvorli merged 3 commits intodotnet:masterfrom
janvorli:fix-watson-buckets-race
Jan 7, 2021
Merged

Fix watson buckets setting race condition#46636
janvorli merged 3 commits intodotnet:masterfrom
janvorli:fix-watson-buckets-race

Conversation

@janvorli
Copy link
Member

@janvorli janvorli commented Jan 6, 2021

The code that updates watson buckets in an exception from its inner
exception has a race that can cause runtime crashes when many threads
are rethrowing the same exception using ExceptionDispatchInfo.Throw.
At several places, we check if the inner exception contains watson
buckets and if that is true, we copy them to the outer exception.
However, the ExceptionDispatchInfo.Throw on another thread can reset
the buckets to the captured value, which might be null. So the
copying then crashes with access violation.

This change fixes the issue by reading the buckets reference once and
then performing the checks / copying using that extracted value.

It also adds a regression test that asserts quickly in debug / checked
builds.

Close #45929

The code that updates watson buckets in an exception from its inner
exception has a race that can cause runtime crashes when many threads
are rethrowing the same exception using ExceptionDispatchInfo.Throw.
At several places, we check if the inner exception contains watson
buckets and if that is true, we copy them to the outer exception.
However, the ExceptionDispatchInfo.Throw on another thread can reset
the buckets to the captured value, which might be null. So the
copying then crashes with access violation.

This change fixes the issue by reading the buckets reference once and
then performing the checks / copying using that extracted value.

It also adds a regression test that asserts quickly in debug / checked
builds.
@janvorli janvorli added this to the 6.0.0 milestone Jan 6, 2021
@janvorli janvorli requested a review from jkotas January 6, 2021 16:22
@janvorli janvorli self-assigned this Jan 6, 2021
@@ -0,0 +1,124 @@
using System;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: License header

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, all of my recent regression tests are missing licence header. I'll fix them.

{
LIMITED_METHOD_CONTRACT;
return _watsonBuckets;
PTR_U1Array buckets = (PTR_U1Array)OBJECTREFToObject(_watsonBuckets);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this change needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that the underlying REF constructor that is called to create the return value calls validation on the _watsonBuckets this way:

do {if ((objref) != NULL) (objref).Validate();} while (0)

During the race, the condition is true, but another thread sets the buckets to NULL right after the check, so the (objref).Validate() crashes. This change prevents that by extracting the object and then constructing the returned value on that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be better to fix the validation to avoid this race condition. I suspect a bunch of places throughout the code have the same problem.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable. I'll give it a try.


if (gotWatsonBucketDetails)
{
// Set the flag that we got bucketing details for the exception
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation looks off

* Make the VALIDATEOBJECTREF race-resilient
* Add license header to the test
* Fix indentation

// the while (0) syntax below is to force a trailing semicolon on users of the macro
#define VALIDATEOBJECT(obj) do {if ((obj) != NULL) (obj)->Validate();} while (0)
#define VALIDATEOBJECTREF(objref) do { VALIDATEOBJECT(OBJECTREFToObject(objref)); } while (0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this fixing the race condition? I would expect to see a temporary local variable or something like that as part of the fix.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sigh, you are right, I have somehow forgotten that this is a macro and not a function call and so the OBJECTREFToObject(objref) will get propagated into the other macro as is.

@janvorli janvorli merged commit f33dd09 into dotnet:master Jan 7, 2021
@janvorli janvorli deleted the fix-watson-buckets-race branch January 7, 2021 16:44
@ghost ghost locked as resolved and limited conversation to collaborators Feb 6, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AccessViolationException 0xc0000005 in CopyWatsonBucketsBetweenThrowables / coreclr.dll

2 participants