VYPR
High severity8.2GHSA Advisory· Published Jun 11, 2026

MessagePack's LZ4 decompression may fail with AccessViolationException after dereferencing memory from bad input

CVE-2026-48109

Description

A remote attacker can trigger out-of-bounds reads in MessagePack's LZ4 decompression, causing denial of service and potential memory disclosure.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A remote attacker can trigger out-of-bounds reads in MessagePack's LZ4 decompression, causing denial of service and potential memory disclosure.

Vulnerability

A vulnerability exists in the optional LZ4 decompression path used by MessagePack compression modes Lz4Block and Lz4BlockArray. The decoder is based on a deprecated fast-decompression algorithm that does not enforce a source-length bound. A remote attacker can send a crafted MessagePack payload with manipulated LZ4 token/length fields to force out-of-bounds reads from the compressed input buffer. This affects applications that deserialize untrusted data while LZ4 compression is enabled. Affected versions: v2 < 2.5.301 and v3 >= 3.0.214-rc.1, < 3.1.7 [1][2].

Exploitation

An attacker needs network access to send a crafted MessagePack payload to a vulnerable endpoint. No authentication is required if the endpoint accepts untrusted data. The attacker manipulates LZ4 token and length fields in the compressed payload to cause the decoder to read beyond the allocated input buffer. This triggers an AccessViolationException during decompression, leading to process termination. Under some conditions, limited unintended memory disclosure from over-read data may also occur before the exception [1][2].

Impact

Successful exploitation results in denial of service via process termination due to AccessViolationException. Additionally, limited memory disclosure may be possible from out-of-bounds reads, potentially leaking sensitive data from the process memory. The impact is primarily availability, with a secondary confidentiality risk [1][2].

Mitigation

Patched versions are available: v2.5.301 and v3.1.7 [1][2]. If upgrading is not possible, workarounds include disabling LZ4 compression for untrusted input paths (Lz4Block, Lz4BlockArray), accepting compressed payloads only from strongly trusted producers, or isolating deserialization in a separate process/container with restart supervision to limit availability impact [1][2].

AI Insight generated on Jun 11, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2

Patches

2
719e690abae8

Bound LZ4 input reads for CWE-125

https://github.com/messagepack-csharp/messagepack-csharpAndrew ArnottApr 29, 2026Fixed in 3.1.7via llm-release-walk
4 files changed · +125 18
  • src/MessagePack/LZ4/LZ4Codec.Unsafe32.Dirty.cs+41 8 modified
    @@ -600,6 +600,7 @@ private static unsafe int LZ4_compress64kCtx_32(
     
             private static unsafe int LZ4_uncompress_32(
                 byte* src,
    +            int src_len,
                 byte* dst,
                 int dst_len)
             {
    @@ -609,6 +610,7 @@ private static unsafe int LZ4_uncompress_32(
                     {
                         // r93
                         var src_p = src;
    +                    var src_end = src + src_len;
                         byte* xxx_ref;
     
                         var dst_p = dst;
    @@ -627,16 +629,26 @@ private static unsafe int LZ4_uncompress_32(
                             int length;
     
                             // get runlength
    +                        if (src_p >= src_end)
    +                        {
    +                            goto _output_error;
    +                        }
    +
                             xxx_token = *src_p++;
                             if ((length = (int)(xxx_token >> ML_BITS)) == RUN_MASK)
                             {
                                 int len;
    -                            for (; (len = *src_p++) == 255; length += 255)
    +                            do
                                 {
    -                                /* do nothing */
    -                            }
    +                                if (src_p >= src_end)
    +                                {
    +                                    goto _output_error;
    +                                }
     
    -                            length += len;
    +                                len = *src_p++;
    +                                length += len;
    +                            }
    +                            while (len == 255);
                             }
     
                             // copy literals
    @@ -649,11 +661,21 @@ private static unsafe int LZ4_uncompress_32(
                                     goto _output_error; // Error : not enough place for another match (min 4) + 5 literals
                                 }
     
    +                            if (length > src_end - src_p)
    +                            {
    +                                goto _output_error;
    +                            }
    +
                                 BlockCopy32(src_p, dst_p, length);
                                 src_p += length;
                                 break; // EOF
                             }
     
    +                        if (length > src_end - src_p)
    +                        {
    +                            goto _output_error;
    +                        }
    +
                             do
                             {
                                 *(uint*)dst_p = *(uint*)src_p;
    @@ -668,6 +690,11 @@ private static unsafe int LZ4_uncompress_32(
                             dst_p = dst_cpy;
     
                             // get offset
    +                        if (src_end - src_p < 2)
    +                        {
    +                            goto _output_error;
    +                        }
    +
                             xxx_ref = dst_cpy - (*(ushort*)src_p);
                             src_p += 2;
                             if (xxx_ref < dst)
    @@ -678,12 +705,18 @@ private static unsafe int LZ4_uncompress_32(
                             // get matchlength
                             if ((length = (int)(xxx_token & ML_MASK)) == ML_MASK)
                             {
    -                            for (; *src_p == 255; length += 255)
    +                            int len;
    +                            do
                                 {
    -                                src_p++;
    -                            }
    +                                if (src_p >= src_end)
    +                                {
    +                                    goto _output_error;
    +                                }
     
    -                            length += *src_p++;
    +                                len = *src_p++;
    +                                length += len;
    +                            }
    +                            while (len == 255);
                             }
     
                             // copy repeated sequence
    
  • src/MessagePack/LZ4/LZ4Codec.Unsafe64.Dirty.cs+41 8 modified
    @@ -612,6 +612,7 @@ private static unsafe int LZ4_compress64kCtx_64(
     
             private static unsafe int LZ4_uncompress_64(
                 byte* src,
    +            int src_len,
                 byte* dst,
                 int dst_len)
             {
    @@ -622,6 +623,7 @@ private static unsafe int LZ4_uncompress_64(
                     {
                         // r93
                         var src_p = src;
    +                    var src_end = src + src_len;
                         byte* dst_ref;
     
                         var dst_p = dst;
    @@ -640,16 +642,26 @@ private static unsafe int LZ4_uncompress_64(
                             int length;
     
                             // get runlength
    +                        if (src_p >= src_end)
    +                        {
    +                            goto _output_error;
    +                        }
    +
                             token = *src_p++;
                             if ((length = token >> ML_BITS) == RUN_MASK)
                             {
                                 int len;
    -                            for (; (len = *src_p++) == 255; length += 255)
    +                            do
                                 {
    -                                /* do nothing */
    -                            }
    +                                if (src_p >= src_end)
    +                                {
    +                                    goto _output_error;
    +                                }
     
    -                            length += len;
    +                                len = *src_p++;
    +                                length += len;
    +                            }
    +                            while (len == 255);
                             }
     
                             // copy literals
    @@ -662,11 +674,21 @@ private static unsafe int LZ4_uncompress_64(
                                     goto _output_error; // Error : not enough place for another match (min 4) + 5 literals
                                 }
     
    +                            if (length > src_end - src_p)
    +                            {
    +                                goto _output_error;
    +                            }
    +
                                 BlockCopy64(src_p, dst_p, length);
                                 src_p += length;
                                 break; // EOF
                             }
     
    +                        if (length > src_end - src_p)
    +                        {
    +                            goto _output_error;
    +                        }
    +
                             do
                             {
                                 *(ulong*)dst_p = *(ulong*)src_p;
    @@ -678,6 +700,11 @@ private static unsafe int LZ4_uncompress_64(
                             dst_p = dst_cpy;
     
                             // get offset
    +                        if (src_end - src_p < 2)
    +                        {
    +                            goto _output_error;
    +                        }
    +
                             dst_ref = dst_cpy - (*(ushort*)src_p);
                             src_p += 2;
                             if (dst_ref < dst)
    @@ -688,12 +715,18 @@ private static unsafe int LZ4_uncompress_64(
                             // get matchlength
                             if ((length = token & ML_MASK) == ML_MASK)
                             {
    -                            for (; *src_p == 255; length += 255)
    +                            int len;
    +                            do
                                 {
    -                                src_p++;
    -                            }
    +                                if (src_p >= src_end)
    +                                {
    +                                    goto _output_error;
    +                                }
     
    -                            length += *src_p++;
    +                                len = *src_p++;
    +                                length += len;
    +                            }
    +                            while (len == 255);
                             }
     
                             // copy repeated sequence
    
  • src/MessagePack/LZ4/LZ4Codec.Unsafe.cs+2 2 modified
    @@ -99,11 +99,11 @@ public static unsafe int Decode(ReadOnlySpan<byte> input, Span<byte> output)
                     int length;
                     if (IntPtr.Size == 4)
                     {
    -                    length = LZ4_uncompress_32(inputPtr, outputPtr, output.Length);
    +                    length = LZ4_uncompress_32(inputPtr, input.Length, outputPtr, output.Length);
                     }
                     else
                     {
    -                    length = LZ4_uncompress_64(inputPtr, outputPtr, output.Length);
    +                    length = LZ4_uncompress_64(inputPtr, input.Length, outputPtr, output.Length);
                     }
     
                     if (length != input.Length)
    
  • tests/MessagePack.Tests/LZ4Test.cs+41 0 modified
    @@ -32,6 +32,47 @@ public void Lz4Compress()
                 Execute(10000);
             }
     
    +        [Fact]
    +        [Trait("CWE", "125")]
    +        public void Lz4BlockRejectsTruncatedLiteralRun()
    +        {
    +            const int extensionByteCount = 1024;
    +            int uncompressedLength = 15 + (255 * extensionByteCount) + 16;
    +
    +            byte[] sizeHeader =
    +            {
    +                0xCE,
    +                (byte)(uncompressedLength >> 24),
    +                (byte)(uncompressedLength >> 16),
    +                (byte)(uncompressedLength >> 8),
    +                (byte)uncompressedLength,
    +            };
    +
    +            byte[] lz4 = new byte[1 + extensionByteCount];
    +            lz4[0] = 0xF0;
    +            for (int i = 1; i < lz4.Length; i++)
    +            {
    +                lz4[i] = 0xFF;
    +            }
    +
    +            int bodyLength = sizeHeader.Length + lz4.Length;
    +            byte[] payload = new byte[6 + bodyLength];
    +            int offset = 0;
    +            payload[offset++] = 0xC9;
    +            payload[offset++] = (byte)(bodyLength >> 24);
    +            payload[offset++] = (byte)(bodyLength >> 16);
    +            payload[offset++] = (byte)(bodyLength >> 8);
    +            payload[offset++] = (byte)bodyLength;
    +            payload[offset++] = 99;
    +            System.Array.Copy(sizeHeader, 0, payload, offset, sizeHeader.Length);
    +            offset += sizeHeader.Length;
    +            System.Array.Copy(lz4, 0, payload, offset, lz4.Length);
    +
    +            var options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4Block);
    +
    +            Assert.Throws<MessagePackSerializationException>(() => MessagePackSerializer.Deserialize<object>(payload, options));
    +        }
    +
             private void Execute(int count)
             {
                 // Large
    
16d4e743ca2a

Vulnerability mechanics

Root cause

"Missing source-length bound in the LZ4 block decoder allows out-of-bounds reads from the compressed input buffer."

Attack vector

A remote attacker sends a crafted MessagePack payload with manipulated LZ4 token, literal-length, offset, or match-length fields. The decoder's unsafe methods advance through the compressed buffer without a source-length bound, so malformed fields drive unchecked native reads past the end of the input buffer. This can trigger an `AccessViolationException` (denial of service) and, under some conditions, limited unintended memory disclosure before the process terminates. The attack requires only that the application deserializes untrusted data with LZ4 compression enabled.

Affected code

The vulnerability resides in the LZ4 decompression path used by `MessagePackCompression.Lz4Block` and `Lz4BlockArray`. The unsafe `LZ4_uncompress_32` and `LZ4_uncompress_64` methods in `LZ4Codec.Unsafe32.Dirty.cs` and `LZ4Codec.Unsafe64.Dirty.cs` accepted only the destination length and advanced through the compressed input without knowing where the source buffer ended, allowing out-of-bounds reads before the existing post-decode length check ran.

What the fix does

The patch adds a `src_len` parameter to both `LZ4_uncompress_32` and `LZ4_uncompress_64` and computes `src_end = src + src_len`. Before every token read, literal-length extension byte read, literal copy, offset read, and match-length extension byte read, the code now checks whether `src_p >= src_end` or whether the remaining source bytes are sufficient. If a check fails, execution jumps to `_output_error`, which causes the decoder to reject the malformed block as a normal serialization exception instead of reading out of bounds. The `Decode` method in `LZ4Codec.Unsafe.cs` is updated to pass `input.Length` as the new `src_len` argument.

Preconditions

  • configThe application must deserialize untrusted data using MessagePack compression modes Lz4Block or Lz4BlockArray.
  • networkThe attacker must be able to send a crafted MessagePack payload over the network or other input channel.

Generated on Jun 11, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.