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

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

Open
ericstj opened this issue May 6, 2024 · 2 comments

Comments

@ericstj
Copy link
Member

ericstj commented May 6, 2024

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:

internal void AdvanceDataStreamIfNeeded()
{
if (_previouslyReadEntry == null)
{
return;
}
if (_archiveStream.CanSeek)
{
Debug.Assert(_previouslyReadEntry._header._endOfHeaderAndDataAndBlockAlignment > 0);
_archiveStream.Position = _previouslyReadEntry._header._endOfHeaderAndDataAndBlockAlignment;
}
else if (_previouslyReadEntry._header._size > 0)
{
// When working with seekable streams, every time we return an entry, we avoid advancing the pointer beyond the data section
// This is so the user can read the data if desired. But if the data was not read by the user, we need to advance the pointer
// here until it's located at the beginning of the next entry header.
// This should only be done if the previous entry came from a TarReader and it still had its original SubReadStream or SeekableSubReadStream.
if (_previouslyReadEntry._header._dataStream is not SubReadStream dataStream)
{
return;
}
if (!dataStream.HasReachedEnd)
{
// If the user did not advance the position, we need to make sure the position
// pointer is located at the beginning of the next header.
if (dataStream.Position < (_previouslyReadEntry._header._size - 1))
{
long bytesToSkip = _previouslyReadEntry._header._size - dataStream.Position;
TarHelpers.AdvanceStream(_archiveStream, bytesToSkip);
dataStream.HasReachedEnd = true; // Now the pointer is beyond the limit, so any read attempts should throw
}
}
TarHelpers.SkipBlockAlignmentPadding(_archiveStream, _previouslyReadEntry._header._size);
}
}

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 dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label May 6, 2024
Copy link
Contributor

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

@tmds
Copy link
Member

tmds commented May 7, 2024

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:

// If the user did not advance the position, we need to make sure the position
// pointer is located at the beginning of the next header.
if (dataStream.Position < (_previouslyReadEntry._header._size - 1))
{
long bytesToSkip = _previouslyReadEntry._header._size - dataStream.Position;
TarHelpers.AdvanceStream(_archiveStream, bytesToSkip);
dataStream.HasReachedEnd = true; // Now the pointer is beyond the limit, so any read attempts should throw
}

@jeffhandley jeffhandley added this to the Future milestone Jul 20, 2024
@jeffhandley jeffhandley removed the untriaged New issue has not been triaged by the area owner label Jul 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants