Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix use-after-free metadata corruption in C# when receiving response headers for streaming response calls #27382

Conversation

jtattermusch
Copy link
Contributor

@jtattermusch jtattermusch commented Sep 17, 2021

Fixes #25219 and #25004 (also see internal b/158826903)

I've been trying to debug this problem for long time, and I finally found the rootcause (with help of modifications that allowed me to reproduce more consistently and with more logging, see #23564).

The problem is that for calls with streaming response (server streaming and full duplex), gRPC C# registers for "recv_response_headers_on_client" event when the call starts and when the duration of the call is very short, sometimes the "recv_response_headers_on_client" can be delivered AFTER receiving all the responses and after delivering the final status of the call. If that happens, the call handle gets disposed (which destroys the underlying grpc_call native object), which can trigger unreferencing of the metadata that hasn't yet been delivered to the C# layer.
So in the "HandleReceivedResponseHeaders", when the initial metadata is being read from the native grpc_metadata_array object, it can be already unreffed by C core and due to the "use-after-free" it's basically changing under our hands as we read it. This was the reason why malformed metadata was being encountered in the InteropClientServerTest.CustomMetadata test.

This problem only affected calls with streaming response that 1. read the response headers 2. have a very short duration (so that the response headers that happens at the very beginning of the call can be delivered after all the responses and final status have been delivered - this won't happen for long lived calls), so hopefully the bug hasn't affected many users in practice.

@jtattermusch jtattermusch added release notes: yes Indicates if PR needs to be in release notes lang/C# priority/P1 labels Sep 17, 2021
@jtattermusch jtattermusch changed the title fix use-after-free metadata corruption in C# when receiving response … fix use-after-free metadata corruption in C# when receiving response headers for streaming response calls Sep 17, 2021
@jtattermusch jtattermusch changed the title fix use-after-free metadata corruption in C# when receiving response headers for streaming response calls Fix use-after-free metadata corruption in C# when receiving response headers for streaming response calls Sep 17, 2021
@jtattermusch
Copy link
Contributor Author

@donnadionne we should probably include this fix in v1.41.x release once it gets merged.

@@ -62,6 +62,7 @@ internal abstract class AsyncCallBase<TWrite, TRead> : IReceivedMessageCallback,

protected bool initialMetadataSent;
protected long streamingWritesCounter; // Number of streaming send operations started so far.
protected bool receiveResponseHeadersPending; // True if this is a call with streaming response and the recv_initial_metadata_on_client operation hasn't finished yet.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this comment should read, "True if this is a server-streaming or bidi-streaming call...."

since this isn't used for client streaming calls

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"a call with streaming response" is a bit clumsy way of saying "server-streaming or bidi-streaming call", so strictly speaking the meaning is identical, but I agree that your wording is better.

@@ -236,6 +236,7 @@ public void StartServerStreamingCall(TRequest msg)
Initialize(details.Channel.CompletionQueue);

halfcloseRequested = true;
receiveResponseHeadersPending = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

UnsafeSerialize can throw, right? Looks like receiveResponseHeadersPending would never be unset in that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that's right, but unless callStartedOk = true is set (and it's only set if all the previous steps succeed), the finally statement will run OnFailedToStartCallLocked(); , which automatically disposes the call. In that case the value of receiveResponseHeadersPending won't matter at all (since the call is already destroyed).

@jtattermusch
Copy link
Contributor Author

@jtattermusch jtattermusch merged commit 2fc133b into grpc:master Sep 18, 2021
@copybara-service copybara-service bot added the imported Specifies if the PR has been imported to the internal repository label Sep 20, 2021
lidizheng pushed a commit to lidizheng/grpc that referenced this pull request Sep 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
imported Specifies if the PR has been imported to the internal repository lang/C# priority/P1 release notes: yes Indicates if PR needs to be in release notes
Projects
None yet
2 participants