abpframework/abp · critical · AbpException

The data is too large: the maximum chunk count has been…

Error message

The data is too large: the maximum chunk count has been exceeded!

What it means

The per-BLOB nonce is an 8-byte random base plus a 4-byte chunk index. If the index becomes negative (i.e. it has overflowed the signed 32-bit range), the same nonce would repeat for the same key, which catastrophically breaks AES-GCM confidentiality. CreateChunkNonce refuses to build such a nonce and throws AbpException.

Solutions

  1. Increase the configured chunk size so the content fits within int.MaxValue chunks.
  2. Split the content into multiple BLOBs each under the chunk-count ceiling.
  3. Cap upload size at the application layer based on chunkSize * int.MaxValue.
  4. Audit chunkSize configuration — it should be in the multi-MB range, not KB.

Example fix

// before
Configure<AbpBlobStoringEncryptionOptions>(o =>
{
    o.ChunkSize = 1024; // 1 KB -> overflows at ~2 TB
});

// after
Configure<AbpBlobStoringEncryptionOptions>(o =>
{
    o.ChunkSize = 4 * 1024 * 1024; // 4 MB -> overflows at ~8 PB
});
Defensive patterns

Strategy: validation

Validate before calling

// Compute the maximum content size for a chunk size and refuse oversized uploads.
static readonly long MaxEncryptedContentBytes =
    (long)AbpBlobStoringEncryptionOptions.DefaultChunkSize * int.MaxValue;

static void AssertSize(long contentLength, int chunkSize)
{
    var max = (long)chunkSize * int.MaxValue;
    if (contentLength > max)
        throw new InvalidOperationException(
            $"Content ({contentLength} B) exceeds the encrypted-BLOB limit ({max} B) for chunkSize={chunkSize}.");
}

Type guard

// Validate chunk size at configuration time.
public sealed record ChunkSize
{
    public int Value { get; }
    public ChunkSize(int bytes)
    {
        if (bytes < 1024 || bytes > 64 * 1024 * 1024)
            throw new ArgumentOutOfRangeException(nameof(bytes), "Use a chunk size between 1 KB and 64 MB.");
        Value = bytes;
    }
}

Try / catch

try
{
    await blob.SaveAsync(name, bigStream);
}
catch (AbpException ex) when (ex.Message.Contains("maximum chunk count has been exceeded"))
{
    // Not retryable as-is: the content is too large for the configured chunk size.
    logger.LogError(ex, "Increase ChunkSize or split the content before retrying.");
    throw;
}

Prevention

When it happens

Trigger: Streaming content whose chunk count exceeds int.MaxValue (~2.1 billion chunks) through the encrypting path, so CreateChunkNonce is called with a wrapped (negative) chunkIndex.

Common situations: A very small configured chunk size combined with extremely large content (e.g. 1 KB chunk size on multi-TB BLOBs); a runaway/miscalibrated producer feeding a tiny chunkSize.

Related errors


AI-assisted analysis of abpframework/abp@7ed43b1931 (2026-08-13). Data as JSON: /api/errors/e1875f9cdde2dcd2. Report an issue: GitHub.

Appendix: source

Thrown at framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobEncryptionCodec.cs:477

    internal static void VerifyTerminalRecordCore(IDisposable cipher, byte[] associatedData, byte[] nonce, byte[] tag)
    {
#if NETSTANDARD2_0
        throw new PlatformNotSupportedException("AES-GCM is not available on .NET Standard 2.0!");
#else
        // Throws CryptographicException if the tag is invalid.
        ((AesGcm)cipher).Decrypt(nonce, Array.Empty<byte>(), tag, Array.Empty<byte>(), associatedData);
#endif
    }

    // Nonce = 8-byte random base + 4-byte chunk index; the per-BLOB key (random salt)
    // makes cross-BLOB reuse harmless and the index keeps it unique within the BLOB.
    internal static byte[] CreateChunkNonce(byte[] baseNonce, int chunkIndex)
    {
        if (chunkIndex < 0)
        {
            // A wrapped chunk index would repeat a nonce for the same key, which breaks AES-GCM.
            throw new AbpException("The data is too large: the maximum chunk count has been exceeded!");
        }

        var nonce = new byte[GcmNonceSize];
        Array.Copy(baseNonce, 0, nonce, 0, BaseNonceSize);
        WriteInt32BigEndian(nonce, BaseNonceSize, chunkIndex);
        return nonce;
    }

    internal static byte[] CreateChunkAssociatedData(byte[] associatedDataPrefix, int chunkIndex)
    {
        var associatedData = new byte[associatedDataPrefix.Length + 4];
        Array.Copy(associatedDataPrefix, 0, associatedData, 0, associatedDataPrefix.Length);
        WriteInt32BigEndian(associatedData, associatedDataPrefix.Length, chunkIndex);
        return associatedData;
    }

    // A stream builds one nonce and one associated-data buffer with these, then rewrites only
    // the trailing chunk index per chunk with WriteChunkIndex; both hold the index as their

View on GitHub (pinned to 7ed43b1931)