Skip null data (fix NRE) in output data received handler#11448
Skip null data (fix NRE) in output data received handler#11448daxian-dbw merged 9 commits intoPowerShell:masterfrom
Conversation
src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs
Show resolved
Hide resolved
|
|
||
| private void OnOutputDataReceived(object sender, DataReceivedEventArgs e) | ||
| { | ||
| if (string.IsNullOrEmpty(e.Data)) |
There was a problem hiding this comment.
Please remove this code, and add this null check to line 749 above.
There was a problem hiding this comment.
What should GetMessageGuid() return in the case? Guid.Empty?
There was a problem hiding this comment.
Yes. Ensuing data processing will check for null or bad packets and do the appropriate thing. Thanks!
|
@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.
Please add try/catch. This method should not throw.
There was a problem hiding this comment.
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.
Both Slice() and IndexOf() methods can throw.
There was a problem hiding this comment.
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.
Please move string null/empty check to here.
There was a problem hiding this comment.
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.
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.
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.
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.
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.
Since this is only used for routing, any invalid packet can be routed to command thread ... it really shouldn't matter.
| // 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.
Please change to use new IsCommandMessage().
| } | ||
|
|
||
| // 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.
Please remove this comment.
I mean please just remove this comment:
// Session messages have empty Guid values.
because it seems unnecessary now.
| { | ||
| try | ||
| { | ||
| if (string.IsNullOrEmpty(data)) |
There was a problem hiding this comment.
Minor comment. The null check does not need to be within the try/catch.
| private const string SESSIONDMESSAGETAG = "PSGuid='00000000-0000-0000-0000-000000000000'"; | ||
|
|
||
| private Guid GetMessageGuid(string data) | ||
| private bool IsSessionMessage(ReadOnlySpan<char> data) |
There was a problem hiding this comment.
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.
:-) Yes, but we again would care about null to exclude a regression in future.
There was a problem hiding this comment.
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.
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.
Inlined the method.
There was a problem hiding this comment.
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.
@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.
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,.ps1and.psm1files have the correct copyright headerWIP:or[ WIP ]to the beginning of the title (theWIPbot will keep its status check atPendingwhile the prefix is present) and remove the prefix when the PR is ready.