-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Skip null data (fix NRE) in output data received handler #11448
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
Skip null data (fix NRE) in output data received handler #11448
Conversation
src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs
Show resolved
Hide resolved
@@ -1217,6 +1219,11 @@ protected override void CleanupConnection() | |||
|
|||
private void OnOutputDataReceived(object sender, DataReceivedEventArgs e) | |||
{ | |||
if (string.IsNullOrEmpty(e.Data)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this code, and add this null check to line 749 above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should GetMessageGuid() return in the case? Guid.Empty?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Ensuing data processing will check for null or bad packets and do the appropriate thing. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@PaulHigin Make sense to optimize GetMessageGuid() method? Is it on hot path? I could exclude allocations (we don't use parsed GUID) and try-catch. |
@iSazonov I don't believe there will be any noticeable perf impact, but it feels cleaner. To summarize, I think we should move the null check from line 749 to line 773. Do you agree? |
If we silently drop the packet it seems we hide a bug. If I undenstand right the packet is created on server before the send code - and if we created a broken package this looks like a bug. |
I added an optimization commit to avoid extra allocations. If you don't like it I will revert. |
@iSazonov |
|
||
private Guid GetMessageGuid(string data) | ||
private bool ContainsMessageGuid(ReadOnlySpan<char> data) | ||
{ | ||
// Perform quick scan for data packet for a GUID, ignoring any errors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add try/catch. This method should not throw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the new implementation the method can not throw. But I added explicit try-catch to avoid a regression if anybody change the method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both Slice() and IndexOf() methods can throw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems ReadOnlySpan.IndexOf() never throw. Slice() could be but IndexOf() before it protects from this.
In any case I already added try-catch to exclude a regression in future..
@@ -775,15 +766,15 @@ protected void HandleOutputDataReceived(string data) | |||
{ | |||
// Route protocol message based on whether it is a session or command message. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move string null/empty check to here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the explicit check.
src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs
Show resolved
Hide resolved
// Ignore any malformed packet errors here and return an empty Guid. | ||
// Packet errors will be reported later during message processing. | ||
var psGuidString = data.Slice(iTag + GUIDTAG.Length); | ||
return Guid.TryParse(psGuidString, out _); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Span.Slice() is a good optimization. But this code is actually incorrect as is. All PSRP packets contain a Guid. But a session message guid is always an empty guid (which is why the original code compares to an empty Guid). A non-empty guid means the PSRP packet is a command message. This method is used to route the message to the appropriate processing thread. To fix this, please make the following changes;
private bool IsCommandMessage(ReadOnlySpan<char> data)
{
try
{
// Perform quick scan for data packet GUID.
var iTag = data.Index(GUIDTAG, StringComparison.OrdinalIgnoreCase);
if (iTag > -1)
{
// An empty GUID value means this is a session message, otherwise it is a command message.
var psGuidString = data.Slice(iTag + GUIDTag.Length, GUID_STR_LEN);
if (Guid.TryParse(psGuidString out Guid psGuid))
{
return !Guid.Equals(psGuid, Guid.Empty);
}
}
}
catch
{
// The method should never throw.
}
// Missing Guid means an invalid packet. Continue processing as session message.
return false;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I did know. Thanks!
Do we know that the package integrity already was checked and GUID always present? If yes we could only search the const without parsing:
return Guid.TryParse(psGuidString, out _); | |
private const string GUIDTAG = "PSGuid=00000000-0000-0000-0000-000000000000'"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, good point. We can simply search for an empty PSGuid element. All valid packets will have a PSGuid
element, and if invalid we just treat as a session message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can make it even simpler. I now agree that the method won't throw so we can remove the try/catch. Also empty Guid equates to session message so we should name the method correctly. I was thinking:
private const string SESSIONGUID = "PSGuid='00000000-0000-0000-0000-000000000000'";
private bool IsSessionMessage(string data)
{
// Perform quick scan for an empty GUID element, which identifies a session message.
return data.IndexOf(SESSIONGUID, StringComparison.OrdinalIgnoreCase) > -1;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is only used for routing, any invalid packet can be routed to command thread ... it really shouldn't matter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// Route protocol message based on whether it is a session or command message. | ||
// Session messages have empty Guid values. | ||
if (Guid.Equals(GetMessageGuid(data), Guid.Empty)) | ||
if (ContainsMessageGuid(data)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please change to use new IsCommandMessage()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// In this case we simply ignore the packet. | ||
return; | ||
} | ||
|
||
// Route protocol message based on whether it is a session or command message. | ||
// Session messages have empty Guid values. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this comment.
I mean please just remove this comment:
// Session messages have empty Guid values.
because it seems unnecessary now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -773,17 +771,25 @@ protected void HandleOutputDataReceived(string data) | |||
{ | |||
try | |||
{ | |||
if (string.IsNullOrEmpty(data)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor comment. The null check does not need to be within the try/catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
|
||
private Guid GetMessageGuid(string data) | ||
private bool IsSessionMessage(ReadOnlySpan<char> data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious if we still need to pass as Span type. We can just scan a normal string type, but I am not sure if it really makes a difference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:-) Yes, but we again would care about null to exclude a regression in future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if you worry that a null value may be passed to this method, the intent is more clear by having a null check here:
private bool IsSessionMessage(string data)
{
if (data == null) { return false; }
return data.IndexOf(SESSIONDMESSAGETAG, StringComparison.OrdinalIgnoreCase) > -1;
}
Or, maybe this method should just be directly inlined in HandleOutputDataReceived
given it's just one line of code and not used anywhere else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer having the private method since it makes clear what the purpose is. Null check is not needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inlined the method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, @PaulHigin sorry I did not see your comment. Perhaps SESSIONDMESSAGETAG well says that the purpose is and inlining is goog? Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PaulHigin Would it be OK to add a comment to the inlined code to make the purpose clear?
I don't mind having it a separate private method, just thought using string data
should be sufficient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assumed the compiler would inline but I am fine either way. These are minor issues and overall I am happy with the changes.
@PaulHigin Thanks for help! |
@iSazonov Thanks for fixing this! |
🎉 Handy links: |
PR Summary
Add null check in OnOutputDataReceived() method.
PR Context
Fix #11445. The NRE comes from #11380.
PR Checklist
.h
,.cpp
,.cs
,.ps1
and.psm1
files have the correct copyright headerWIP:
or[ WIP ]
to the beginning of the title (theWIP
bot will keep its status check atPending
while the prefix is present) and remove the prefix when the PR is ready.