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

Json Arithmetic operation resulted in an overflow #609

Closed
iSazonov opened this issue Dec 6, 2019 · 14 comments · Fixed by #1308 or #34040
Closed

Json Arithmetic operation resulted in an overflow #609

iSazonov opened this issue Dec 6, 2019 · 14 comments · Fixed by #1308 or #34040
Assignees
Labels
area-System.Text.Json enhancement Product code improvement that does NOT require public API changes/additions good first issue Issue should be easy to implement, good for first-time contributors
Milestone

Comments

@iSazonov
Copy link
Contributor

iSazonov commented Dec 6, 2019

Experimenting with new Json API in PowerShell Core repo I get very large output and as result exception "Arithmetic operation resulted in an overflow".

The exception message looks non-user-friendly and non-useful.

Proposal:

  • make the exception and message more informative in Json code. ("Result exceeded maximum buffer size (2 Gb?)")
  • make the exception and message more informative in System.Buffers code too.

Additional information

The exception comes from

int newSize = checked(_buffer.Length + growBy);

Stack trace from PowerShell:

PS > $previous | ConvertTo-Json -Depth 1000 | Out-File -Path c:\temp\q25.txt
ConvertTo-Json: Arithmetic operation resulted in an overflow.

PS > Get-Error

Exception             :
    Type       : System.OverflowException
    TargetSite :
        Name          : CheckAndResizeBuffer
        DeclaringType : System.Text.Json.PooledByteBufferWriter
        MemberType    : Method
        Module        : System.Text.Json.dll
    StackTrace :
   at System.Text.Json.PooledByteBufferWriter.CheckAndResizeBuffer(Int32 sizeHint)
   at System.Text.Json.PooledByteBufferWriter.GetMemory(Int32 sizeHint)
   at System.Text.Json.Utf8JsonWriter.Grow(Int32 requiredSize)
   at System.Text.Json.Utf8JsonWriter.WriteLiteralIndented(ReadOnlySpan`1 escapedPropertyName, ReadOnlySpan`1 value)
   at System.Text.Json.Utf8JsonWriter.WriteNull(JsonEncodedText propertyName)
   at System.Text.Json.WriteStackFrame.WriteObjectOrArrayStart(ClassType classType, JsonEncodedText propertyName, Utf8JsonWriter writer, Boolean writeNull)
   at System.Text.Json.WriteStackFrame.WriteObjectOrArrayStart(ClassType classType, Utf8JsonWriter writer, JsonSerializerOptions options, Boolean writeNull)
   at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.JsonSerializer.WriteCore(Utf8JsonWriter writer, Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Serialize[TValue](Utf8JsonWriter writer, TValue value, JsonSerializerOptions options)
   at Microsoft.PowerShell.Commands.JsonObject.SerializePsProperties(Utf8JsonWriter writer, PSObject pso, Object obj, Boolean isCustomObj, JsonSerializerOptions options)
in C:\Users\sie\Documents\GitHub\iSazonov\PowerShell\src\Microsoft.PowerShell.Commands.Utility\commands\utility\WebCmdlet\JsonObject.cs:line 682
   at Microsoft.PowerShell.Commands.JsonObject.JsonConverterPSObject.Write(Utf8JsonWriter writer, PSObject pso, JsonSerializerOptions options) in
C:\Users\sie\Documents\GitHub\iSazonov\PowerShell\src\Microsoft.PowerShell.Commands.Utility\commands\utility\WebCmdlet\JsonObject.cs:line 641
   at System.Text.Json.JsonPropertyInfoNotNullable`4.OnWrite(WriteStackFrame& current, Utf8JsonWriter writer)
   at System.Text.Json.JsonPropertyInfo.Write(WriteStack& state, Utf8JsonWriter writer)
   at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.JsonSerializer.WriteCore(Utf8JsonWriter writer, Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.WriteCore(PooledByteBufferWriter output, Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.WriteCoreString(Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Serialize(Object value, Type inputType, JsonSerializerOptions options)
   at Microsoft.PowerShell.Commands.JsonObject.ConvertToJson(Object objectToProcess, ConvertToJsonContext& context) in
C:\Users\sie\Documents\GitHub\iSazonov\PowerShell\src\Microsoft.PowerShell.Commands.Utility\commands\utility\WebCmdlet\JsonObject.cs:line 501
   at Microsoft.PowerShell.Commands.ConvertToJsonCommand.EndProcessing() in
C:\Users\sie\Documents\GitHub\iSazonov\PowerShell\src\Microsoft.PowerShell.Commands.Utility\commands\utility\WebCmdlet\ConvertToJsonCommand.cs:line 122
   at System.Management.Automation.Cmdlet.DoEndProcessing() in C:\Users\sie\Documents\GitHub\iSazonov\PowerShell\src\System.Management.Automation\engine\cmdlet.cs:line 187
   at System.Management.Automation.CommandProcessorBase.Complete() in
C:\Users\sie\Documents\GitHub\iSazonov\PowerShell\src\System.Management.Automation\engine\CommandProcessorBase.cs:line 590
    Message    : Arithmetic operation resulted in an overflow.
    Source     : System.Text.Json
    HResult    : -2146233066
CategoryInfo          : NotSpecified: (:) [ConvertTo-Json], OverflowException
FullyQualifiedErrorId : System.OverflowException,Microsoft.PowerShell.Commands.ConvertToJsonCommand
InvocationInfo        :
    MyCommand        : ConvertTo-Json
    ScriptLineNumber : 1
    OffsetInLine     : 13
    HistoryId        : 17
    Line             : $previous | ConvertTo-Json -Depth 1000 | Out-File -Path c:\temp\q25.txt
    PositionMessage  : At line:1 char:13
                       + $previous | ConvertTo-Json -Depth 1000 | Out-File -Path c:\temp\q25.t …
                       +             ~~~~~~~~~~~~~~~~~~~~~~~~~~
    InvocationName   : ConvertTo-Json
    CommandOrigin    : Internal
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1

PowerShell can be downloaded from the PR PowerShell/PowerShell#11198
Repo scripts:

            $start = 1
            $previous = @{
                Depth = 0
                Next1 = $null
                Next2 = $null
            }

            25..$start | ForEach-Object {
                $current = @{
                    Depth = $_
                    Next1 = $previous
                    Next2 = $previous
                }
                $previous = $current
            }

$previous | ConvertTo-Json -Depth 1000 | Out-File -Path c:\temp\q25.txt
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Text.Json untriaged New issue has not been triaged by the area owner labels Dec 6, 2019
@ahsonkhan
Copy link
Member

The exception comes from

int newSize = checked(_buffer.Length + growBy);

This particular exception (at least within the JSON serializer code) comes from the internal PooledByteBufferWriter:

Proposal:

  • make the exception and message more informative in Json code. ("Result exceeded maximum buffer size (2 Gb?)")
  • make the exception and message more informative in System.Buffers code too.

Both of those seem reasonable to me. Adding an if-check on the size that throws makes sense (though I don't know if, in some contexts, checking/throwing might introduce noticeable overhead). That said, it is possible that we have other edge cases where checked arithmetic is used to guard against integer overflow or OOM when asking for buffers that won't fit in a T[] or sizes that the arraypool can't honor.

Anyone interested in a PR with tests?

@ahsonkhan ahsonkhan added this to the 5.0 milestone Dec 9, 2019
@ahsonkhan ahsonkhan added help wanted [up-for-grabs] Good issue for external contributors enhancement Product code improvement that does NOT require public API changes/additions good first issue Issue should be easy to implement, good for first-time contributors and removed untriaged New issue has not been triaged by the area owner labels Dec 9, 2019
@felipepessoto
Copy link
Contributor

@ahsonkhan I'm interested, could you assign me?

@ahsonkhan ahsonkhan removed the help wanted [up-for-grabs] Good issue for external contributors label Dec 24, 2019
@ahsonkhan
Copy link
Member

@felipepessoto done.

@felipepessoto
Copy link
Contributor

@ahsonkhan currently the checked keyword only validates the integer overflow (if the value would exceed int.MaxValue).
But ArrayPool.Shared.Rent won't work for int.MaxValue anyway, because we can't allocate new byte[] of int.MaxValue size (I believe that is because of array headers overhead). Throwing another non-user-friendly exception in rare occasions where the newSize is equal to int.MaxSize or very close to it.

In this case, should we validate if the newSize is smaller than (int.MaxValue - 56) (for 64 bit process)?
For 32bits processes, I guess the header is a bit smaller. But it will generate an OutOfMemoryException anyway, unless we set the LARGEADDRESSAWARE flag (I haven't tested this scenario)

@sywhang

@mikedn
Copy link
Contributor

mikedn commented Jan 5, 2020

because we can't allocate new byte[] of int.MaxValue size (I believe that is because of array headers overhead)

It's a bit more complicated than headers overhead.

https://docs.microsoft.com/en-us/dotnet/api/system.array?view=netcore-3.1

The array size is limited to a total of 4 billion elements, and to a maximum index of 0X7FEFFFFF in any given dimension (0X7FFFFFC7 for byte arrays and arrays of single-byte structures).

@felipepessoto
Copy link
Contributor

Nice, I didn't know that. So we can compare it to Array.MaxByteArrayLength or 0X7FFFFFC7

@felipepessoto
Copy link
Contributor

Another question, throwing JsonException with a custom message is ok for this scenario? Or should I create a specific Exception?

Thanks

@iSazonov
Copy link
Contributor Author

iSazonov commented Jan 6, 2020

@felipepessoto The question about Json exceptions is tracked in #610

@mikedn
Copy link
Contributor

mikedn commented Jan 6, 2020

So we can compare it to Array.MaxByteArrayLength or 0X7FFFFFC7

Using MaxByteArrayLength would be better but AFAIK it's internal to corelib and this code seems to be in another assembly.

Actually, just clamping the result to int.MaxValue might be enough - if you attempt to allocate an int.MaxValue length array the runtime will generate:

System.OutOfMemoryException: Array dimensions exceeded supported range.

which seems like a reasonable error to me.

@felipepessoto
Copy link
Contributor

@mikedn, if we let the array constructor throw the OutOfMemoryException, we'll be back to the original problem where the Exception message is not clear. I could validate the size and throw an OutOfMemoryException with a custom message.

Stephen also gave a suggestion here: #1308 (comment)

@mikedn
Copy link
Contributor

mikedn commented Jan 6, 2020

we'll be back to the original problem where the Exception message is not clear

I don't think so, the original message is "Arithmetic operation resulted in an overflow." which is indeed not very clear, it's not obvious to anyone what the cause of the problem is. The "Array dimensions exceeded supported range" message should be obvious enough. Unless you really want to tell the user - "Hey, your JSON is yugeeee!" - so there's definitely no confusion :)

Besides, this looks very much like a corner case. The fact that a 2GB JSON is first generated in memory and then written to a file seems pretty dubious to me.

@felipepessoto
Copy link
Contributor

we'll be back to the original problem where the Exception message is not clear

I don't think so, the original message is "Arithmetic operation resulted in an overflow." which is indeed not very clear, it's not obvious to anyone what the cause of the problem is. The "Array dimensions exceeded supported range" message should be obvious enough. Unless you really want to tell the user - "Hey, your JSON is yugeeee!" - so there's definitely no confusion :)

Besides, this looks very much like a corner case. The fact that a 2GB JSON is first generated in memory and then written to a file seems pretty dubious to me.

You are right. Agreed

@ahsonkhan
Copy link
Member

Re-opening until #32587 is done.

@ahsonkhan ahsonkhan reopened this Feb 20, 2020
felipepessoto added a commit to felipepessoto/runtime that referenced this issue Mar 20, 2020
We throw a OutOfMemoryException instead of "Arithmetic operation resulted in an overflow"
@ahsonkhan
Copy link
Member

Re-opening until #34040 is done.

@ahsonkhan ahsonkhan reopened this Mar 25, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 11, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Text.Json enhancement Product code improvement that does NOT require public API changes/additions good first issue Issue should be easy to implement, good for first-time contributors
Projects
None yet
5 participants