VYPR
Medium severity5.3NVD Advisory· Published May 5, 2026· Updated May 6, 2026

CVE-2026-43868

CVE-2026-43868

Description

Memory Allocation with Excessive Size Value vulnerability in Apache Thrift.

This issue affects Apache Thrift: before 0.23.0.

Users are recommended to upgrade to version 0.23.0, which fixes the issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
thriftcrates.io
<= 0.22.0

Affected products

2
  • Apache/Thriftinferred2 versions
    <0.23.0+ 1 more
    • (no CPE)range: <0.23.0
    • cpe:2.3:a:apache:thrift:*:*:*:*:*:*:*:*range: <0.23.0

Patches

1
d5152211af61

Add byte-count limit to TCompactProtocol varint reader

https://github.com/apache/thriftJens GeyerApr 21, 2026via ghsa
21 files changed · +416 88
  • lib/dart/lib/src/protocol/t_compact_protocol.dart+7 6 modified
    @@ -39,6 +39,7 @@ class TCompactProtocol extends TProtocol {
       static const int TYPE_MASK = 0xE0;
       static const int TYPE_BITS = 0x07;
       static const int TYPE_SHIFT_AMOUNT = 5;
    +  static const int MAX_VARINT_BYTES = 10; // ceil(64/7); matches protobuf wire format
       static final TField TSTOP = TField("", TType.STOP, 0);
     
       static const int TYPE_BOOLEAN_TRUE = 0x01;
    @@ -427,25 +428,25 @@ static final List<int> _typeMap = List<int>.unmodifiable(
       Int32 _readVarInt32() {
         Int32 result = Int32.ZERO;
         int shift = 0;
    -    while (true) {
    +    for (int idx = 0; idx < MAX_VARINT_BYTES; idx++) {
           Int32 b = Int32(readByte());
           result |= (b & 0x7f) << shift;
    -      if ((b & 0x80) != 0x80) break;
    +      if ((b & 0x80) != 0x80) return result;
           shift += 7;
         }
    -    return result;
    +    throw TProtocolError(TProtocolErrorType.INVALID_DATA, 'Variable-length int over 10 bytes.');
       }
     
       Int64 _readVarInt64() {
         Int64 result = Int64.ZERO;
         int shift = 0;
    -    while (true) {
    +    for (int idx = 0; idx < MAX_VARINT_BYTES; idx++) {
           Int64 b = Int64(readByte());
           result |= (b & 0x7f) << shift;
    -      if ((b & 0x80) != 0x80) break;
    +      if ((b & 0x80) != 0x80) return result;
           shift += 7;
         }
    -    return result;
    +    throw TProtocolError(TProtocolErrorType.INVALID_DATA, 'Variable-length int over 10 bytes.');
       }
     
       Int32 _zigzagToInt32(Int32 n) {
    
  • lib/dart/test/protocol/t_protocol_test.dart+23 0 modified
    @@ -395,6 +395,29 @@ void main() {
         });
     
         group('shared tests', sharedTests);
    +
    +    test('readI64 rejects overlong varint', () async {
    +      final trans = TBufferedTransport();
    +      final proto = TCompactProtocol(trans);
    +      final bytes = Uint8List(11)..fillRange(0, 11, 0x80);
    +      trans.write(bytes, 0, 11);
    +      await trans.flush();
    +      expect(
    +        () => proto.readI64(),
    +        throwsA(isA<TProtocolError>().having(
    +          (e) => e.type, 'type', TProtocolErrorType.INVALID_DATA)));
    +    });
    +
    +    test('readI64 accepts valid 10-byte varint', () async {
    +      final trans = TBufferedTransport();
    +      final proto = TCompactProtocol(trans);
    +      final bytes = Uint8List(10)
    +        ..fillRange(0, 9, 0x80)
    +        ..[9] = 0x01;
    +      trans.write(bytes, 0, 10);
    +      await trans.flush();
    +      expect(() => proto.readI64(), returnsNormally);
    +    });
       });
     }
     
    
  • lib/delphi/src/Thrift.Protocol.Compact.pas+11 7 modified
    @@ -60,7 +60,9 @@       TFactory = class( TInterfacedObject, IProtocolFactory)
         VERSION_MASK      = Byte( $1F); // 0001 1111
         TYPE_MASK         = Byte( $E0); // 1110 0000
         TYPE_BITS         = Byte( $07); // 0000 0111
    -    TYPE_SHIFT_AMOUNT = Byte( 5);
    +    TYPE_SHIFT_AMOUNT   = Byte( 5);
    +    MAX_VARINT32_BYTES  = Integer( 5);   // ceil(32/7); matches protobuf wire format
    +    MAX_VARINT64_BYTES  = Integer( 10);  // ceil(64/7); matches protobuf wire format
     
       strict private type
         // All of the on-wire type codes.
    @@ -915,36 +917,38 @@ procedure TCompactProtocolImpl.ReadSetEnd;
     // Read an i32 from the wire as a varint. The MSB of each byte is set
     // if there is another byte to follow. This can Read up to 5 bytes.
     function TCompactProtocolImpl.ReadVarint32 : Cardinal;
    -var shift : Integer;
    +var shift, idx : Integer;
         b : Byte;
     begin
       result := 0;
       shift  := 0;
    -  while TRUE do begin
    +  for idx := 0 to MAX_VARINT32_BYTES - 1 do begin
         b := Byte( ReadByte);
         result := result or (Cardinal(b and $7F) shl shift);
         if ((b and $80) <> $80)
    -    then Break;
    +    then Exit;
         Inc( shift, 7);
       end;
    +  raise TProtocolExceptionInvalidData.Create('Variable-length int over 5 bytes.');
     end;
     
     
     // Read an i64 from the wire as a proper varint. The MSB of each byte is set
     // if there is another byte to follow. This can Read up to 10 bytes.
     function TCompactProtocolImpl.ReadVarint64 : UInt64;
    -var shift : Integer;
    +var shift, idx : Integer;
         b : Byte;
     begin
       result := 0;
       shift  := 0;
    -  while TRUE do begin
    +  for idx := 0 to MAX_VARINT64_BYTES - 1 do begin
         b := Byte( ReadByte);
         result := result or (UInt64(b and $7F) shl shift);
         if ((b and $80) <> $80)
    -    then Break;
    +    then Exit;
         Inc( shift, 7);
       end;
    +  raise TProtocolExceptionInvalidData.Create('Variable-length int over 10 bytes.');
     end;
     
     
    
  • lib/erl/src/thrift_compact_protocol.erl+17 9 modified
    @@ -129,13 +129,15 @@ to_varint(Value, Acc) when (Value < 16#80) -> [Acc, Value];
     to_varint(Value, Acc) -> to_varint(Value bsr 7, [Acc, ((Value band 16#7F) bor 16#80)]).
     
     -spec read_varint(t_compact(), non_neg_integer(), non_neg_integer()) ->
    -    {t_compact(), {'ok', integer()}}.
    -read_varint(This0, Acc, Count) ->
    +    {t_compact(), {'ok', integer()} | {error, term()}}.
    +read_varint(This0, Acc, Count) when Count < 10 ->
         {This1, {ok, Byte}} = read(This0, byte),
         case (Byte band 16#80) of
             0 -> {This1, {ok, (Byte bsl (7 * Count)) + Acc}};
             _ -> read_varint(This1, ((Byte band 16#7f) bsl (7 * Count)) + Acc, Count + 1)
    -    end.
    +    end;
    +read_varint(This0, _Acc, _Count) ->
    +    {This0, {error, {invalid_data, "Variable-length int exceeds 10 bytes"}}}.
     
     write(This0, #protocol_message_begin{
         name = Name,
    @@ -348,16 +350,22 @@ read(This0, byte) ->
             Else -> {This1, Else}
         end;
     read(This0, i16) ->
    -    {This1, {ok, Zigzag}} = read_varint(This0, 0, 0),
    -    {This1, {ok, from_zigzag(Zigzag)}};
    +    case read_varint(This0, 0, 0) of
    +        {This1, {ok, Zigzag}} -> {This1, {ok, from_zigzag(Zigzag)}};
    +        {This1, {error, _} = Err} -> {This1, Err}
    +    end;
     read(This0, ui32) ->
         read_varint(This0, 0, 0);
     read(This0, i32) ->
    -    {This1, {ok, Zigzag}} = read_varint(This0, 0, 0),
    -    {This1, {ok, from_zigzag(Zigzag)}};
    +    case read_varint(This0, 0, 0) of
    +        {This1, {ok, Zigzag}} -> {This1, {ok, from_zigzag(Zigzag)}};
    +        {This1, {error, _} = Err} -> {This1, Err}
    +    end;
     read(This0, i64) ->
    -    {This1, {ok, Zigzag}} = read_varint(This0, 0, 0),
    -    {This1, {ok, from_zigzag(Zigzag)}};
    +    case read_varint(This0, 0, 0) of
    +        {This1, {ok, Zigzag}} -> {This1, {ok, from_zigzag(Zigzag)}};
    +        {This1, {error, _} = Err} -> {This1, Err}
    +    end;
     read(This0, double) ->
         {This1, Bytes} = read_data(This0, 8),
         case Bytes of
    
  • lib/go/thrift/compact_protocol.go+7 3 modified
    @@ -766,23 +766,27 @@ func (p *TCompactProtocol) readVarint32() (int32, error) {
     	return int32(v), err
     }
     
    +// maxVarint64Bytes is the maximum wire size of a varint-encoded 64-bit integer:
    +// ceil(64/7) = 10 bytes, matching the protobuf wire-format specification.
    +const maxVarint64Bytes = 10
    +
     // Read an i64 from the wire as a proper varint. The MSB of each byte is set
     // if there is another byte to follow. This can read up to 10 bytes.
     func (p *TCompactProtocol) readVarint64() (int64, error) {
     	shift := uint(0)
     	result := int64(0)
    -	for {
    +	for rsize := 0; rsize < maxVarint64Bytes; rsize++ {
     		b, err := p.readByteDirect()
     		if err != nil {
     			return 0, err
     		}
     		result |= int64(b&0x7f) << shift
     		if (b & 0x80) != 0x80 {
    -			break
    +			return result, nil
     		}
     		shift += 7
     	}
    -	return result, nil
    +	return 0, NewTProtocolExceptionWithType(INVALID_DATA, errors.New("variable-length int over 10 bytes"))
     }
     
     // Read a byte, unlike ReadByte that reads Thrift-byte that is i8.
    
  • lib/go/thrift/compact_protocol_test.go+24 0 modified
    @@ -24,6 +24,30 @@ import (
     	"testing"
     )
     
    +func TestCompactProtocolVarintRejectsOverlong(t *testing.T) {
    +	// 11 continuation bytes (bit 7 set), no terminating byte
    +	payload := bytes.Repeat([]byte{0x80}, 11)
    +	trans := NewTMemoryBufferLen(len(payload))
    +	trans.Write(payload)
    +	p := NewTCompactProtocol(trans)
    +	_, err := p.readVarint64()
    +	if err == nil {
    +		t.Fatal("expected error for varint over 10 bytes, got nil")
    +	}
    +}
    +
    +func TestCompactProtocolVarintAcceptsValid10Byte(t *testing.T) {
    +	// 9 continuation bytes followed by a terminating byte
    +	payload := append(bytes.Repeat([]byte{0x80}, 9), 0x01)
    +	trans := NewTMemoryBufferLen(len(payload))
    +	trans.Write(payload)
    +	p := NewTCompactProtocol(trans)
    +	_, err := p.readVarint64()
    +	if err != nil {
    +		t.Fatalf("unexpected error for valid 10-byte varint: %v", err)
    +	}
    +}
    +
     func TestReadWriteCompactProtocol(t *testing.T) {
     	ReadWriteProtocolTest(t, NewTCompactProtocolFactory())
     
    
  • lib/haxe/src/org/apache/thrift/protocol/TCompactProtocol.hx+7 7 modified
    @@ -50,6 +50,7 @@ class TCompactProtocol extends TProtocolImplBase implements TProtocol {
         private static inline var TYPE_MASK : Int = 0xE0; // 1110 0000
         private static inline var TYPE_BITS : Int = 0x07; // 0000 0111
         private static inline var TYPE_SHIFT_AMOUNT : Int = 5;
    +    private static inline var MAX_VARINT_BYTES : Int = 10; // ceil(64/7); matches protobuf wire format
     
     
         private static var ttypeToCompactType = [
    @@ -650,15 +651,15 @@ class TCompactProtocol extends TProtocolImplBase implements TProtocol {
         private function ReadVarint32() : UInt {
             var result : UInt = 0;
             var shift : Int = 0;
    -        while (true) {
    +        for (idx in 0 ... MAX_VARINT_BYTES) {
                 var b : Int = readByte();
                 result |= cast((b & 0x7f) << shift, UInt);
                 if ((b & 0x80) != 0x80) {
    -                break;
    +                return result;
                 }
                 shift += 7;
             }
    -        return result;
    +        throw new TProtocolException( TProtocolException.INVALID_DATA, "Variable-length int over 10 bytes.");
         }
     
         /**
    @@ -668,16 +669,15 @@ class TCompactProtocol extends TProtocolImplBase implements TProtocol {
         private function ReadVarint64() : Int64 {
             var shift : Int = 0;
             var result : Int64 = Int64.make(0,0);
    -        while (true) {
    +        for (idx in 0 ... MAX_VARINT_BYTES) {
                 var b : Int = readByte();
                 result = Int64.or( result, Int64.shl( Int64.make(0,b & 0x7f), shift));
                 if ((b & 0x80) != 0x80) {
    -                break;
    +                return result;
                 }
                 shift += 7;
             }
    -
    -        return result;
    +        throw new TProtocolException( TProtocolException.INVALID_DATA, "Variable-length int over 10 bytes.");
         }
     
     
    
  • lib/java/src/main/java/org/apache/thrift/protocol/TCompactProtocol.java+22 6 modified
    @@ -93,6 +93,10 @@ public TProtocol getProtocol(TTransport trans) {
       private static final byte TYPE_BITS = 0x07; // 0000 0111
       private static final int TYPE_SHIFT_AMOUNT = 5;
     
    +  // Maximum wire bytes for a varint-encoded integer: ceil(32/7)=5, ceil(64/7)=10.
    +  private static final int MAX_VARINT32_BYTES = 5;
    +  private static final int MAX_VARINT64_BYTES = 10;
    +
       /** All of the on-wire type codes. */
       private static class Types {
         public static final byte BOOLEAN_TRUE = 0x01;
    @@ -750,7 +754,7 @@ public void readSetEnd() throws TException {}
       private int readVarint32() throws TException {
         int result = 0;
         int shift = 0;
    -    if (trans_.getBytesRemainingInBuffer() >= 5) {
    +    if (trans_.getBytesRemainingInBuffer() >= MAX_VARINT32_BYTES) {
           byte[] buf = trans_.getBuffer();
           int pos = trans_.getBufferPosition();
           int off = 0;
    @@ -760,15 +764,21 @@ private int readVarint32() throws TException {
             if ((b & 0x80) != 0x80) break;
             shift += 7;
             off++;
    +        if (off >= MAX_VARINT32_BYTES) {
    +          throw new TProtocolException(
    +              TProtocolException.INVALID_DATA, "Variable-length int over 5 bytes.");
    +        }
           }
           trans_.consumeBuffer(off + 1);
         } else {
    -      while (true) {
    +      for (int rsize = 0; rsize < MAX_VARINT32_BYTES; rsize++) {
             byte b = readByte();
             result |= (b & 0x7f) << shift;
    -        if ((b & 0x80) != 0x80) break;
    +        if ((b & 0x80) != 0x80) return result;
             shift += 7;
           }
    +      throw new TProtocolException(
    +          TProtocolException.INVALID_DATA, "Variable-length int over 5 bytes.");
         }
         return result;
       }
    @@ -780,7 +790,7 @@ private int readVarint32() throws TException {
       private long readVarint64() throws TException {
         int shift = 0;
         long result = 0;
    -    if (trans_.getBytesRemainingInBuffer() >= 10) {
    +    if (trans_.getBytesRemainingInBuffer() >= MAX_VARINT64_BYTES) {
           byte[] buf = trans_.getBuffer();
           int pos = trans_.getBufferPosition();
           int off = 0;
    @@ -790,15 +800,21 @@ private long readVarint64() throws TException {
             if ((b & 0x80) != 0x80) break;
             shift += 7;
             off++;
    +        if (off >= MAX_VARINT64_BYTES) {
    +          throw new TProtocolException(
    +              TProtocolException.INVALID_DATA, "Variable-length int over 10 bytes.");
    +        }
           }
           trans_.consumeBuffer(off + 1);
         } else {
    -      while (true) {
    +      for (int rsize = 0; rsize < MAX_VARINT64_BYTES; rsize++) {
             byte b = readByte();
             result |= (long) (b & 0x7f) << shift;
    -        if ((b & 0x80) != 0x80) break;
    +        if ((b & 0x80) != 0x80) return result;
             shift += 7;
           }
    +      throw new TProtocolException(
    +          TProtocolException.INVALID_DATA, "Variable-length int over 10 bytes.");
         }
         return result;
       }
    
  • lib/java/src/test/java/org/apache/thrift/protocol/TestTCompactProtocol.java+42 0 modified
    @@ -19,8 +19,14 @@
     
     package org.apache.thrift.protocol;
     
    +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
    +import static org.junit.jupiter.api.Assertions.assertEquals;
    +import static org.junit.jupiter.api.Assertions.assertThrows;
    +
    +import java.util.Arrays;
     import org.apache.thrift.TDeserializer;
     import org.apache.thrift.TException;
    +import org.apache.thrift.transport.TMemoryInputTransport;
     import org.junit.jupiter.api.Test;
     import thrift.test.Bonk;
     
    @@ -49,6 +55,42 @@ public void testOOMDenialOfService() throws Exception {
         }
       }
     
    +  @Test
    +  public void testReadVarint64FastPathRejectsOverlong() throws Exception {
    +    byte[] buf = new byte[11];
    +    Arrays.fill(buf, (byte) 0x80); // 11 continuation bytes, no terminator
    +    TCompactProtocol proto = new TCompactProtocol(new TMemoryInputTransport(buf));
    +    TProtocolException ex = assertThrows(TProtocolException.class, proto::readI64);
    +    assertEquals(TProtocolException.INVALID_DATA, ex.getType());
    +  }
    +
    +  @Test
    +  public void testReadVarint64FastPathAcceptsValid10Byte() throws Exception {
    +    byte[] buf = new byte[10];
    +    Arrays.fill(buf, (byte) 0x80);
    +    buf[9] = 0x01; // terminating byte
    +    TCompactProtocol proto = new TCompactProtocol(new TMemoryInputTransport(buf));
    +    assertDoesNotThrow(proto::readI64);
    +  }
    +
    +  @Test
    +  public void testReadVarint32FastPathRejectsOverlong() throws Exception {
    +    byte[] buf = new byte[6];
    +    Arrays.fill(buf, (byte) 0x80); // 6 continuation bytes, no terminator
    +    TCompactProtocol proto = new TCompactProtocol(new TMemoryInputTransport(buf));
    +    TProtocolException ex = assertThrows(TProtocolException.class, proto::readI32);
    +    assertEquals(TProtocolException.INVALID_DATA, ex.getType());
    +  }
    +
    +  @Test
    +  public void testReadVarint32FastPathAcceptsValid5Byte() throws Exception {
    +    byte[] buf = new byte[5];
    +    Arrays.fill(buf, (byte) 0x80);
    +    buf[4] = 0x01; // terminating byte
    +    TCompactProtocol proto = new TCompactProtocol(new TMemoryInputTransport(buf));
    +    assertDoesNotThrow(proto::readI32);
    +  }
    +
       public static void main(String args[]) throws Exception {
         new TestTCompactProtocol().benchmark();
       }
    
  • lib/lua/TCompactProtocol.lua+10 7 modified
    @@ -29,7 +29,8 @@ TCompactProtocol = __TObject.new(TProtocolBase, {
       COMPACT_VERSION_MASK      = 0x1f,
       COMPACT_TYPE_MASK         = 0xE0,
       COMPACT_TYPE_BITS         = 0x07,
    -  COMPACT_TYPE_SHIFT_AMOUNT = 5,
    +  COMPACT_TYPE_SHIFT_AMOUNT  = 5,
    +  COMPACT_MAX_VARINT_BYTES   = 10, -- ceil(64/7); matches protobuf wire format
     
       -- Used to keep track of the last field for the current and previous structs,
       -- so we can do the delta stuff.
    @@ -445,31 +446,33 @@ end
     function TCompactProtocol:readVarint32()
       local shiftl = 0
       local result = 0
    -  while true do
    +  for idx = 0, self.COMPACT_MAX_VARINT_BYTES - 1 do
         b = self:readByte()
         result = libluabitwise.bor(result,
                  libluabitwise.shiftl(libluabitwise.band(b, 0x7f), shiftl))
         if libluabitwise.band(b, 0x80) ~= 0x80 then
    -      break
    +      return result
         end
         shiftl = shiftl + 7
       end
    -  return result
    +  terror(TProtocolException:new{errorCode = TProtocolException.INVALID_DATA,
    +    message = 'Variable-length int over 10 bytes.'})
     end
     
     function TCompactProtocol:readVarint64()
       local result = liblualongnumber.new
       local data = result(0)
       local shiftl = 0
    -  while true do
    +  for idx = 0, self.COMPACT_MAX_VARINT_BYTES - 1 do
         b = self:readSignByte()
         endFlag, data = libluabpack.fromVarint64(b, shiftl, data)
         shiftl = shiftl + 7
         if endFlag == 0 then
    -      break
    +      return data
         end
       end
    -  return data
    +  terror(TProtocolException:new{errorCode = TProtocolException.INVALID_DATA,
    +    message = 'Variable-length int over 10 bytes.'})
     end
     
     function TCompactProtocol:getTType(ctype)
    
  • lib/netstd/Thrift/Protocol/TCompactProtocol.cs+9 6 modified
    @@ -42,6 +42,8 @@ public class TCompactProtocol : TProtocol
             private const byte TypeMask = 0xE0; // 1110 0000
             private const byte TypeBits = 0x07; // 0000 0111
             private const int TypeShiftAmount = 5;
    +        private const int MaxVarint32Bytes = 5;   // ceil(32/7); matches protobuf wire format
    +        private const int MaxVarint64Bytes = 10;  // ceil(64/7); matches protobuf wire format
     
             private const byte NoTypeOverride = 0xFF;
     
    @@ -744,18 +746,18 @@ Read an i32 from the wire as a varint. The MSB of each byte is set
                 uint result = 0;
                 var shift = 0;
     
    -            while (true)
    +            for (var idx = 0; idx < MaxVarint32Bytes; idx++)
                 {
                     var b = (byte) await ReadByteAsync(cancellationToken);
                     result |= (uint) (b & 0x7f) << shift;
                     if ((b & 0x80) != 0x80)
                     {
    -                    break;
    +                    return result;
                     }
                     shift += 7;
                 }
     
    -            return result;
    +            throw new TProtocolException(TProtocolException.INVALID_DATA, "Variable-length int over 5 bytes.");
             }
     
             private async ValueTask<ulong> ReadVarInt64Async(CancellationToken cancellationToken)
    @@ -769,18 +771,19 @@ Read an i64 from the wire as a proper varint. The MSB of each byte is set
     
                 var shift = 0;
                 ulong result = 0;
    -            while (true)
    +
    +            for (var idx = 0; idx < MaxVarint64Bytes; idx++)
                 {
                     var b = (byte) await ReadByteAsync(cancellationToken);
                     result |= (ulong) (b & 0x7f) << shift;
                     if ((b & 0x80) != 0x80)
                     {
    -                    break;
    +                    return result;
                     }
                     shift += 7;
                 }
     
    -            return result;
    +            throw new TProtocolException(TProtocolException.INVALID_DATA, "Variable-length int over 10 bytes.");
             }
     
             private static int ZigzagToInt(uint n)
    
  • lib/php/lib/Protocol/TCompactProtocol.php+4 4 modified
    @@ -64,6 +64,8 @@ class TCompactProtocol extends TProtocol
         const TYPE_BITS = 0x07;
         const TYPE_SHIFT_AMOUNT = 5;
     
    +    const MAX_VARINT_BYTES = 10; // ceil(64/7); matches protobuf wire format
    +
         protected static $ctypes = array(
             TType::STOP => TCompactProtocol::COMPACT_STOP,
             TType::BOOL => TCompactProtocol::COMPACT_TRUE, // used for collection
    @@ -145,7 +147,7 @@ public function readVarint(&$result)
             $idx = 0;
             $shift = 0;
             $result = 0;
    -        while (true) {
    +        while ($idx < self::MAX_VARINT_BYTES) {
                 $x = $this->trans_->readAll(1);
                 $arr = unpack('C', $x);
                 $byte = $arr[1];
    @@ -156,9 +158,7 @@ public function readVarint(&$result)
                 }
                 $shift += 7;
             }
    -
    -        #unreachable statement
    -        return $idx;
    +        throw new TProtocolException('Variable-length int over 10 bytes.', TProtocolException::INVALID_DATA);
         }
     
         public function __construct($trans)
    
  • lib/php/test/Unit/Lib/Protocol/TCompactProtocolTest.php+32 0 modified
    @@ -164,6 +164,38 @@ public function testReadVarint()
             $this->assertSame(1000, $result);
         }
     
    +    public function testReadVarintRejectsOverlong()
    +    {
    +        $transport = $this->createMock(TTransport::class);
    +        $protocol = new TCompactProtocol($transport);
    +
    +        $transport->expects($this->exactly(10))
    +                  ->method('readAll')
    +                  ->with(1)
    +                  ->willReturn("\x80"); // continuation byte — bit 7 always set
    +
    +        $this->expectException(TProtocolException::class);
    +        $this->expectExceptionCode(TProtocolException::INVALID_DATA);
    +        $protocol->readVarint($result);
    +    }
    +
    +    public function testReadVarintAcceptsValid10ByteVarint()
    +    {
    +        $transport = $this->createMock(TTransport::class);
    +        $protocol = new TCompactProtocol($transport);
    +
    +        $continuations = array_fill(0, 9, "\x80");
    +        $continuations[] = "\x01"; // terminating byte
    +
    +        $transport->expects($this->exactly(10))
    +                  ->method('readAll')
    +                  ->with(1)
    +                  ->willReturnOnConsecutiveCalls(...$continuations);
    +
    +        $byteCount = $protocol->readVarint($result);
    +        $this->assertSame(10, $byteCount);
    +    }
    +
         public function testWriteMessageBegin()
         {
             $name = 'testName';
    
  • lib/py/src/protocol/TCompactProtocol.py+6 1 modified
    @@ -69,16 +69,21 @@ def writeVarint(trans, n):
         trans.write(bytes(out))
     
     
    +_MAX_VARINT_BYTES = 10  # ceil(64/7); matches protobuf wire format
    +
    +
     def readVarint(trans):
         result = 0
         shift = 0
    -    while True:
    +    for _ in range(_MAX_VARINT_BYTES):
             x = trans.readAll(1)
             byte = ord(x)
             result |= (byte & 0x7f) << shift
             if byte >> 7 == 0:
                 return result
             shift += 7
    +    raise TProtocolException(TProtocolException.INVALID_DATA,
    +                             "Variable-length int over 10 bytes.")
     
     
     # As per TCompactProtocol.tcc
    
  • lib/py/test/thrift_TCompactProtocol.py+15 0 modified
    @@ -19,6 +19,7 @@
     
     import _import_local_thrift  # noqa
     from thrift.protocol import TCompactProtocol
    +from thrift.protocol.TProtocol import TProtocolException
     from thrift.transport import TTransport
     import unittest
     import uuid
    @@ -301,6 +302,20 @@ def test_TCompactProtocol_write_read(self):
                 print("Assertion fail")
                 raise e
     
    +    def test_readVarint_rejects_overlong_input(self):
    +        payload = bytes([0x80] * 11)  # 11 continuation bytes, no terminator
    +        trans = TTransport.TMemoryBuffer(payload)
    +        with self.assertRaises(TProtocolException) as ctx:
    +            TCompactProtocol.readVarint(trans)
    +        self.assertEqual(ctx.exception.type, TProtocolException.INVALID_DATA)
    +
    +    def test_readVarint_accepts_valid_10_byte_varint(self):
    +        # max valid varint64: 10 bytes, last byte has bit 7 clear
    +        payload = bytes([0x80] * 9 + [0x01])
    +        trans = TTransport.TMemoryBuffer(payload)
    +        result = TCompactProtocol.readVarint(trans)
    +        self.assertIsNotNone(result)
    +
     
     if __name__ == "__main__":
         unittest.main()
    
  • lib/rb/ext/compact_protocol.c+20 5 modified
    @@ -436,22 +436,37 @@ static int32_t zig_zag_to_int(uint32_t n) {
       return (int32_t)((n >> 1) ^ (0U - (n & 1U)));
     }
     
    +#define MAX_VARINT32_BYTES 5  /* ceil(32/7); matches protobuf wire format */
    +#define MAX_VARINT64_BYTES 10 /* ceil(64/7); matches protobuf wire format */
    +
     static uint64_t read_varint64(VALUE self) {
    -  int shift = 0;
    +  int i, shift = 0;
       uint64_t result = 0;
    -  while (true) {
    +  for (i = 0; i < MAX_VARINT64_BYTES; i++) {
         int8_t b = read_byte_direct(self);
         result |= ((uint64_t)(b & 0x7f) << shift);
         if ((b & 0x80) != 0x80) {
    -      break;
    +      return result;
         }
         shift += 7;
       }
    -  return result;
    +  rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_INVALID_DATA), rb_str_new2("Variable-length int over 10 bytes.")));
    +  return 0; /* unreachable */
     }
     
     static uint32_t read_varint32(VALUE self) {
    -  return (uint32_t)read_varint64(self);
    +  int i, shift = 0;
    +  uint32_t result = 0;
    +  for (i = 0; i < MAX_VARINT32_BYTES; i++) {
    +    int8_t b = read_byte_direct(self);
    +    result |= ((uint32_t)(b & 0x7f) << shift);
    +    if ((b & 0x80) != 0x80) {
    +      return result;
    +    }
    +    shift += 7;
    +  }
    +  rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_INVALID_DATA), rb_str_new2("Variable-length int over 5 bytes.")));
    +  return 0; /* unreachable */
     }
     
     static int16_t read_i16(VALUE self) {
    
  • lib/rb/lib/thrift/protocol/compact_protocol.rb+14 4 modified
    @@ -27,6 +27,8 @@ class CompactProtocol < BaseProtocol
         TYPE_MASK = 0xE0
         TYPE_BITS = 0x07
         TYPE_SHIFT_AMOUNT = 5
    +    MAX_VARINT32_BYTES = 5  # ceil(32/7); matches protobuf wire format
    +    MAX_VARINT_BYTES = 10   # ceil(64/7); matches protobuf wire format
     
         TSTOP = [nil, Types::STOP, 0]
     
    @@ -411,19 +413,27 @@ def write_varint64(n)
         end
     
         def read_varint32()
    -      read_varint64()
    +      shift = 0
    +      result = 0
    +      MAX_VARINT32_BYTES.times do
    +        b = read_byte()
    +        result |= (b & 0x7f) << shift
    +        return result if (b & 0x80) != 0x80
    +        shift += 7
    +      end
    +      raise ProtocolException.new(ProtocolException::INVALID_DATA, 'Variable-length int over 5 bytes.')
         end
     
         def read_varint64()
           shift = 0
           result = 0
    -      while true
    +      MAX_VARINT_BYTES.times do
             b = read_byte()
             result |= (b & 0x7f) << shift
    -        break if (b & 0x80) != 0x80
    +        return result if (b & 0x80) != 0x80
             shift += 7
           end
    -      result
    +      raise ProtocolException.new(ProtocolException::INVALID_DATA, 'Variable-length int over 10 bytes.')
         end
     
         def int_to_zig_zag(n)
    
  • lib/rb/spec/compact_protocol_spec.rb+28 0 modified
    @@ -241,6 +241,34 @@
         expect(trans.read(trans.available).unpack("C*")).to eq([0x07, 0x61, 0x62, 0x63, 0x20, 0xE2, 0x82, 0xAC])
       end
     
    +  it "should reject a varint with more than 10 continuation bytes" do
    +    trans = Thrift::MemoryBufferTransport.new(([0x80] * 11).pack("C*"))
    +    proto = Thrift::CompactProtocol.new(trans)
    +    expect { proto.read_i64 }.to raise_error(Thrift::ProtocolException) do |e|
    +      expect(e.type).to eq(Thrift::ProtocolException::INVALID_DATA)
    +    end
    +  end
    +
    +  it "should accept a valid 10-byte varint" do
    +    trans = Thrift::MemoryBufferTransport.new((([0x80] * 9) + [0x01]).pack("C*"))
    +    proto = Thrift::CompactProtocol.new(trans)
    +    expect { proto.read_i64 }.not_to raise_error
    +  end
    +
    +  it "should reject a 32-bit varint with more than 5 continuation bytes" do
    +    trans = Thrift::MemoryBufferTransport.new(([0x80] * 6).pack("C*"))
    +    proto = Thrift::CompactProtocol.new(trans)
    +    expect { proto.read_i32 }.to raise_error(Thrift::ProtocolException) do |e|
    +      expect(e.type).to eq(Thrift::ProtocolException::INVALID_DATA)
    +    end
    +  end
    +
    +  it "should accept a valid 5-byte varint for i32" do
    +    trans = Thrift::MemoryBufferTransport.new((([0x80] * 4) + [0x0f]).pack("C*"))
    +    proto = Thrift::CompactProtocol.new(trans)
    +    expect { proto.read_i32 }.not_to raise_error
    +  end
    +
       class JankyHandler
         def Janky(i32arg)
           i32arg * 2
    
  • lib/rs/src/protocol/compact.rs+77 8 modified
    @@ -16,7 +16,7 @@
     // under the License.
     
     use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
    -use integer_encoding::{VarIntReader, VarIntWriter};
    +use integer_encoding::VarIntWriter;
     use std::convert::{From, TryFrom};
     use std::io;
     
    @@ -31,6 +31,8 @@ use crate::{ProtocolError, ProtocolErrorKind, TConfiguration};
     const COMPACT_PROTOCOL_ID: u8 = 0x82;
     const COMPACT_VERSION: u8 = 0x01;
     const COMPACT_VERSION_MASK: u8 = 0x1F;
    +const MAX_VARINT32_BYTES: usize = 5; // ceil(32/7); matches protobuf wire format
    +const MAX_VARINT64_BYTES: usize = 10; // ceil(64/7); matches protobuf wire format
     
     /// Read messages encoded in the Thrift compact protocol.
     ///
    @@ -101,7 +103,7 @@ where
                 // high bits set high if count and type encoded separately
                 possible_element_count as i32
             } else {
    -            self.transport.read_varint::<u32>()? as i32
    +            self.read_varint32()? as i32
             };
     
             let min_element_size = self.min_serialized_size(element_type);
    @@ -121,6 +123,40 @@ where
             }
             Ok(())
         }
    +
    +    fn read_varint32(&mut self) -> crate::Result<u32> {
    +        let mut result = 0u32;
    +        let mut shift = 0u32;
    +        for _ in 0..MAX_VARINT32_BYTES {
    +            let b = self.read_byte()?;
    +            result |= ((b & 0x7F) as u32) << shift;
    +            if b & 0x80 == 0 {
    +                return Ok(result);
    +            }
    +            shift += 7;
    +        }
    +        Err(crate::Error::Protocol(ProtocolError::new(
    +            ProtocolErrorKind::InvalidData,
    +            "Variable-length int over 5 bytes.",
    +        )))
    +    }
    +
    +    fn read_varint64(&mut self) -> crate::Result<u64> {
    +        let mut result = 0u64;
    +        let mut shift = 0u32;
    +        for _ in 0..MAX_VARINT64_BYTES {
    +            let b = self.read_byte()?;
    +            result |= ((b & 0x7F) as u64) << shift;
    +            if b & 0x80 == 0 {
    +                return Ok(result);
    +            }
    +            shift += 7;
    +        }
    +        Err(crate::Error::Protocol(ProtocolError::new(
    +            ProtocolErrorKind::InvalidData,
    +            "Variable-length int over 10 bytes.",
    +        )))
    +    }
     }
     
     impl<T> TInputProtocol for TCompactInputProtocol<T>
    @@ -156,7 +192,7 @@ where
             // NOTE: unsigned right shift will pad with 0s
             let message_type: TMessageType = TMessageType::try_from(type_and_byte >> 5)?;
             // writing side wrote signed sequence number as u32 to avoid zigzag encoding
    -        let sequence_number = self.transport.read_varint::<u32>()? as i32;
    +        let sequence_number = self.read_varint32()? as i32;
             let service_call_name = self.read_string()?;
     
             self.last_read_field_id = 0;
    @@ -265,7 +301,7 @@ where
         }
     
         fn read_bytes(&mut self) -> crate::Result<Vec<u8>> {
    -        let len = self.transport.read_varint::<u32>()?;
    +        let len = self.read_varint32()?;
     
             if let Some(max_size) = self.config.max_string_size() {
                 if len as usize > max_size {
    @@ -291,15 +327,15 @@ where
         }
     
         fn read_i16(&mut self) -> crate::Result<i16> {
    -        self.transport.read_varint::<i16>().map_err(From::from)
    +        Ok(zigzag_to_i32(self.read_varint32()?) as i16)
         }
     
         fn read_i32(&mut self) -> crate::Result<i32> {
    -        self.transport.read_varint::<i32>().map_err(From::from)
    +        Ok(zigzag_to_i32(self.read_varint32()?))
         }
     
         fn read_i64(&mut self) -> crate::Result<i64> {
    -        self.transport.read_varint::<i64>().map_err(From::from)
    +        Ok(zigzag_to_i64(self.read_varint64()?))
         }
     
         fn read_double(&mut self) -> crate::Result<f64> {
    @@ -340,7 +376,7 @@ where
         }
     
         fn read_map_begin(&mut self) -> crate::Result<TMapIdentifier> {
    -        let element_count = self.transport.read_varint::<u32>()? as i32;
    +        let element_count = self.read_varint32()? as i32;
             if element_count == 0 {
                 Ok(TMapIdentifier::new(None, None, 0))
             } else {
    @@ -397,6 +433,16 @@ pub(crate) fn compact_protocol_min_serialized_size(field_type: TType) -> usize {
         }
     }
     
    +#[inline]
    +fn zigzag_to_i32(n: u32) -> i32 {
    +    ((n >> 1) as i32) ^ (0i32.wrapping_sub((n & 1) as i32))
    +}
    +
    +#[inline]
    +fn zigzag_to_i64(n: u64) -> i64 {
    +    ((n >> 1) as i64) ^ (0i64.wrapping_sub((n & 1) as i64))
    +}
    +
     impl<T> io::Seek for TCompactInputProtocol<T>
     where
         T: io::Seek + TReadTransport,
    @@ -2907,6 +2953,29 @@ mod tests {
             assert!(i_prot.read_map_end().is_ok()); // will blow up if we try to read from empty buffer
         }
     
    +    #[test]
    +    fn must_reject_overlong_varint_i64() {
    +        let (mut i_prot, _) = test_objects();
    +        i_prot.transport.set_readable_bytes(&[0x80u8; 11]); // 11 continuation bytes, no terminator
    +        let res = i_prot.read_i64();
    +        assert!(res.is_err());
    +        match res {
    +            Err(crate::Error::Protocol(e)) => {
    +                assert_eq!(e.kind, ProtocolErrorKind::InvalidData);
    +            }
    +            _ => panic!("expected Protocol/InvalidData error"),
    +        }
    +    }
    +
    +    #[test]
    +    fn must_accept_valid_10_byte_varint_i64() {
    +        let (mut i_prot, _) = test_objects();
    +        let mut bytes = [0x80u8; 10];
    +        bytes[9] = 0x01; // terminating byte
    +        i_prot.transport.set_readable_bytes(&bytes);
    +        assert!(i_prot.read_i64().is_ok());
    +    }
    +
         fn test_objects() -> (
             TCompactInputProtocol<ReadHalf<TBufferChannel>>,
             TCompactOutputProtocol<WriteHalf<TBufferChannel>>,
    
  • lib/swift/Sources/TCompactProtocol.swift+13 14 modified
    @@ -47,7 +47,8 @@ public class TCompactProtocol: TProtocol {
       public static let protocolID: UInt8  = 0x82
       public static let version: UInt8     = 1
       public static let versionMask: UInt8 = 0x1F // 0001 1111
    -  
    +  static let maxVarintBytes = 10 // ceil(64/7); matches protobuf wire format
    +
       public var transport: TTransport
       
       var lastField: [UInt8] = []
    @@ -139,35 +140,33 @@ public class TCompactProtocol: TProtocol {
       func readVarint32() throws -> UInt32 {
         var result: UInt32 = 0
         var shift: UInt32 = 0
    -    while true {
    +    for _ in 0 ..< TCompactProtocol.maxVarintBytes {
           let byte: UInt8 = try read()
    -      
    +
           result |= UInt32(byte & 0x7F) << shift
           if (byte & 0x80) == 0 {
    -        break
    +        return result
           }
    -      
    +
           shift += 7
         }
    -    
    -    return result
    +    throw TProtocolError(error: .invalidData, message: "Variable-length int over 10 bytes.")
       }
    -  
    +
       func readVarint64() throws -> UInt64 {
         var result: UInt64 = 0
         var shift: UInt64 = 0
    -    
    -    while true {
    +    for _ in 0 ..< TCompactProtocol.maxVarintBytes {
           let byte: UInt8 = try read()
    -      
    +
           result |= UInt64(byte & 0x7F) << shift
           if (byte & 0x80) == 0 {
    -        break
    +        return result
           }
    -      
    +
           shift += 7
         }
    -    return result
    +    throw TProtocolError(error: .invalidData, message: "Variable-length int over 10 bytes.")
       }
       
     
    
  • lib/swift/Tests/ThriftTests/TCompactProtocolTests.swift+28 1 modified
    @@ -211,6 +211,31 @@ class TCompactProtocolTests: XCTestCase {
         XCTAssertEqual(proto.zigZagToi64(UInt64.max), min, "Error 64bit zigzag on \(min)")
       }
       
    +  func testVarintRejectsOverlong() {
    +    let buf = Data(repeating: 0x80, count: 11) // 11 continuation bytes, no terminator
    +    let t = TMemoryBufferTransport(readBuffer: buf)
    +    let p = TCompactProtocol(on: t)
    +    do {
    +      let _: Int64 = try p.read()
    +      XCTFail("Expected TProtocolError but no error was thrown")
    +    } catch let protoError as TProtocolError {
    +      guard case .invalidData = protoError.error else {
    +        XCTFail("Expected .invalidData, got \(protoError.error)"); return
    +      }
    +    } catch {
    +      XCTFail("Expected TProtocolError, got \(error)")
    +    }
    +  }
    +
    +  func testVarintAcceptsValid10Byte() {
    +    var bytes = [UInt8](repeating: 0x80, count: 9)
    +    bytes.append(0x01) // terminating byte
    +    let buf = Data(bytes)
    +    let t = TMemoryBufferTransport(readBuffer: buf)
    +    let p = TCompactProtocol(on: t)
    +    XCTAssertNoThrow(try p.read() as Int64)
    +  }
    +
       static var allTests : [(String, (TCompactProtocolTests) -> () throws -> Void)] {
         return [
           ("testInt8WriteRead", testInt8WriteRead),
    @@ -223,7 +248,9 @@ class TCompactProtocolTests: XCTestCase {
           ("testDataWriteRead", testDataWriteRead),
           ("testStructWriteRead", testStructWriteRead),
           ("testInt32ZigZag", testInt32ZigZag),
    -      ("testInt64ZigZag", testInt64ZigZag)
    +      ("testInt64ZigZag", testInt64ZigZag),
    +      ("testVarintRejectsOverlong", testVarintRejectsOverlong),
    +      ("testVarintAcceptsValid10Byte", testVarintAcceptsValid10Byte)
         ]
       }
     }
    

Vulnerability mechanics

AI mechanics synthesis has not run for this CVE yet.

References

4

News mentions

1