dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.96k stars 4.65k forks source link

Disposing TarEntry.DataStream causes TarReader.GetNextEntry to throw ObjectDisposedException #101948

Open ericstj opened 4 months ago

ericstj commented 4 months ago

Description

When disposing the stream of a single TarEntry, I cannot advance the TarReader to the next entry.

Consider the following sample:

using (var tarReader = new TarReader(unseekableStream))
{
    TarEntry? entry;
    while ((entry = tarReader.GetNextEntry()) != null)
    {
         Stream s = entry.DataStream;
         s.CopyTo(someOtherStream);
         s.Dispose();
    }
}

This will throw

System.ObjectDisposedException
  HResult=0x80131622
  Message=Cannot access a disposed object.
Object name: 'System.Formats.Tar.SubReadStream'.
  Source=System.Private.CoreLib
  StackTrace:
System.Private.CoreLib.dll!System.ThrowHelper.ThrowObjectDisposedException(object instance) Line 458
    at /_/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs(458)
System.Private.CoreLib.dll!System.ObjectDisposedException.ThrowIf(bool condition, object instance) Line 61
    at /_/src/libraries/System.Private.CoreLib/src/System/ObjectDisposedException.cs(61)
System.Formats.Tar.dll!System.Formats.Tar.SubReadStream.Position.get() Line 52
    at /_/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SubReadStream.cs(52)
System.Formats.Tar.dll!System.Formats.Tar.TarReader.AdvanceDataStreamIfNeeded() Line 228
    at /_/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs(228)
System.Formats.Tar.dll!System.Formats.Tar.TarReader.GetNextEntry(bool copyData) Line 133
    at /_/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs(133)

The suspect code is here: https://github.com/dotnet/runtime/blob/84b33395057737db3ea342a5151feb6b90c1b6f6/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs#L200-L237

Reproduction Steps

See above

Expected behavior

No exception thrown

Actual behavior

ObjectDisposedException

Regression?

No

Known Workarounds

Copy the entry to a memory stream. Copy the archive to a memory stream before opening (so it is seek-able). Etc

Configuration

No response

Other information

I noticed when debugging this that the state of the SubReadStream was the following: image

Note that _positionInSuperStream is the same as _endInSuperStream. I noticed that the HasReachedEnd property is checked before accessing Position which will throw. It seems like HasReachedEnd checks both the fields mentioned, but will only treat it as true if greater, perhaps it should have been greater or equal?

dotnet-policy-service[bot] commented 4 months ago

Tagging subscribers to this area: @dotnet/area-system-formats-tar See info in area-owners.md if you want to be subscribed.

tmds commented 4 months ago

This code determines how much of the superstream was read through the SubReadStream by calling the Position property. That getter throws ODE because the SubReadStream got disposed:

https://github.com/dotnet/runtime/blob/7dc3669ebebcc406b327bbaceae2a300a8b66bd3/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs#L226-L233