Runtime: System.Drawing.Common throws System.NotSupportedException: Stream does not support reading

Created on 12 Mar 2020  Â·  20Comments  Â·  Source: dotnet/runtime

It looks like version from netcoreapp2.0 works but one from netcoreapp3.0 doesn't.
I was told to use one from netcoreapp3.1, but there isn't such folder in the nuget package.
The callstack from our repro:

System.NotSupportedException: Stream does not support reading.
at System.IO.StreamHelpers.ValidateCopyToArgs(Stream source, Stream destination, Int32 bufferSize)
at System.IO.Stream.CopyTo(Stream destination, Int32 bufferSize)
at System.IO.Stream.CopyTo(Stream destination)
at System.Drawing.Internal.GPStream..ctor(Stream stream, Boolean makeSeekable)
at System.Drawing.Image.Save(Stream stream, ImageCodecInfo encoder, EncoderParameters encoderParams)
at System.Drawing.Image.Save(Stream stream, ImageFormat format)
at Microsoft.Exchange.Data.Storage.OleAttachment.ConvertToImage(Stream outStream, ImageFormat format) in H:\subrepo1\src\sources\dev\data\src\storage\Attachments\OleAttachment.cs:line 263
at Microsoft.Exchange.Data.Storage.OleAttachment.TryConvertToImage(Stream outStream, ImageFormat format) in H:\subrepo1\src\sources\dev\data\src\storage\Attachments\OleAttachment.cs:line 70

area-System.Drawing

All 20 comments

@soyang2828 could please provide more details on the repro?
I.e:
A sample code and input of the image?
What OS are you running on?
What version of System.Drawing.Common are you using?

cc: @JeremyKuhne

I was told to use one from netcoreapp3.1

There is no netcoreapp3.1 asset because the implementation for netcoreapp3.1 is the same as for netcoreapp3.0 so it uses the one in netcoreapp3.0.

We would need to know the details asked above to be able to diagnose and understand the problem.

The ask is why it throws when I use assembly under netcoreapp3.0? (callstack in the first post)

I'm still not understanding the issue. Could you provide with more details? If you can't share the project, code sample, etc, we can take this offline and share all that info there 😄.

I don't know why it throws, it might be a bug we introduced, but to understand more we need more info. Like the package version, a simple repro, etc.

@soyang2828 were you able to share this info (eg in email)? We would like to help -- but we need more info.

Sorry for the delay. The OS is windows 10
This repros on: system.drawing.common\4.7.0\runtimes\win\lib\netcoreapp3.0

NOT repro on: system.drawing.common\4.7.0\runtimes\win\lib\netcoreapp2.0

Thanks, @soyang2828 -- we still need more info on the repro, a sample code and sample stream (file used to create the stream) would really help.

Hmm... This was caught in the production and it doesn't repro all the time. I vaguely remembered it crashed when tried to parse a png image file. Would you find anything suspcious from the callstacks?

Is this a Drawing bug? It seems that Microsoft.Exchange.Data.Storage.OleAttachment.ConvertToImage has called System.Drawing.Image.Save(Stream stream, ImageFormat format) passing in a stream that returns false from CanRead property -- ie., it does not support reading (only writing, presumably).

The callstack is very simple -- we are trying to read from the stream passed in, and it's not readable. @soyang2828 can you inspect the calling code and see where the stream comes from?

https://github.com/dotnet/corefx/commit/dee434428ee392302ff2050bbee304c9fce68b2f likely introduced the difference.

Here's the relevant diff:
https://github.com/dotnet/runtime/commit/136527537e68110c0891d911705c50cbbddd829e#diff-110b09b5c69ec04d04ae3f8e569c757bL329-R333

So this previously just exposed the managed stream as an IStream by wrapping with ComStreamFromDataStream, which would have let GDIPlus directly call Read/Write as needed (and maybe it never called read, or it did but handled it not working). By using GPStream here and not specifying makeSeekable = false it ends up trying to copy the user's stream into a MemoryStream.
https://github.com/dotnet/runtime/commit/136527537e68110c0891d911705c50cbbddd829e#diff-928ae7e9a8b8058b941ca631131edf7dL17-L45

I think the fix here may be to change this to pass makeSeekable=false so long as that doesn't invalidate the intent of the original fix. /cc @JeremyKuhne

As @danmosemsft mentioned this is happening as a result of the Stream being passed into System.Drawing.Image.Save(Stream stream, ImageFormat format) returning CanRead as false. So if you have control of that stream you can workaround this bug by passing in a readable stream (eg: a temporary MemoryStream) and doing the copy to the backing stream yourself after calling Save.

Thanks @ericstj -- your links into hte diff above don't seem to navigate to me. Which files in this diff?

I'll pick this up since I pretty much already understand it and the fix.

Thanks @ericstj — nice find!

@ericstj the bigger part of the change was making the interop more performant and trying to avoid unnecessary allocations (in addition to fixing bugs). This call path looks to be an oversight on my part. 🙉 Sort of silly to make a copy of something you're trying to copy into.

Reopening to track servicing fix.

@ericstj this can be closed now the port merged, right?

Should we port this to release/5.0 as well? It seems odd that the 4.7.* package has the fix, but the 5.0.* package doesn't

@ericstj this can be closed now the port merged, right?

Yep!

Should we port this to release/5.0

This was a backport of a fix already in 5.0: https://github.com/dotnet/runtime/pull/36805

This was a backport of a fix already in 5.0: #36805

Ah, I missed that this was before we branched off for release/5.0

Was this page helpful?
0 / 5 - 0 ratings