[PR #1161] Fix ArrayPool corruption from double-disposal in BufferedSubStream #1599

Open
opened 2026-01-29 22:21:18 +00:00 by claunia · 0 comments
Owner

Original Pull Request: https://github.com/adamhathcock/sharpcompress/pull/1161

State: closed
Merged: Yes


BufferedSubStream.Dispose returns a rented array to ArrayPool but lacks guard against multiple disposals. Since _cache was readonly, it couldn't be nulled after return, causing the same array to be returned twice on subsequent Dispose calls—corrupting the pool.

Changes

  • Added disposal guard: _isDisposed flag prevents re-execution of disposal logic
  • Made _cache nullable: Changed from readonly byte[] to byte[]? to enable nulling after pool return
  • Added disposal checks: RefillCache methods throw ObjectDisposedException if called post-disposal
  • Fixed condition order: Check CanSeek before accessing Position to avoid exceptions on non-seekable streams

Pattern

Follows the same disposal pattern as base class SharpCompressStream:

protected override void Dispose(bool disposing)
{
    if (_isDisposed) return;
    _isDisposed = true;
    
    if (disposing && _cache is not null)
    {
        ArrayPool<byte>.Shared.Return(_cache);
        _cache = null;
    }
    base.Dispose(disposing);
}

Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

**Original Pull Request:** https://github.com/adamhathcock/sharpcompress/pull/1161 **State:** closed **Merged:** Yes --- `BufferedSubStream.Dispose` returns a rented array to `ArrayPool` but lacks guard against multiple disposals. Since `_cache` was `readonly`, it couldn't be nulled after return, causing the same array to be returned twice on subsequent `Dispose` calls—corrupting the pool. ## Changes - **Added disposal guard**: `_isDisposed` flag prevents re-execution of disposal logic - **Made `_cache` nullable**: Changed from `readonly byte[]` to `byte[]?` to enable nulling after pool return - **Added disposal checks**: `RefillCache` methods throw `ObjectDisposedException` if called post-disposal - **Fixed condition order**: Check `CanSeek` before accessing `Position` to avoid exceptions on non-seekable streams ## Pattern Follows the same disposal pattern as base class `SharpCompressStream`: ```csharp protected override void Dispose(bool disposing) { if (_isDisposed) return; _isDisposed = true; if (disposing && _cache is not null) { ArrayPool<byte>.Shared.Return(_cache); _cache = null; } base.Dispose(disposing); } ``` <!-- START COPILOT CODING AGENT TIPS --> --- ✨ Let Copilot coding agent [set things up for you](https://github.com/adamhathcock/sharpcompress/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.
claunia added the pull-request label 2026-01-29 22:21:19 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/sharpcompress#1599