This is an automated email from the ASF dual-hosted git repository.

ColinLeeo pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/tsfile.git


The following commit(s) were added to refs/heads/develop by this push:
     new 9755a42a2 fix: validate RLBE decoder run lengths (#913)
9755a42a2 is described below

commit 9755a42a23275fe3eefeeee6ab7ca8b306511ada
Author: Colin Lee <[email protected]>
AuthorDate: Mon Aug 24 14:29:08 2026 +0800

    fix: validate RLBE decoder run lengths (#913)
---
 cpp/src/encoding/rlbe_decoder.h                    | 20 ++++-
 cpp/test/encoding/rlbe_codec_test.cc               | 79 +++++++++++++++++++
 .../tsfile/encoding/decoder/IntRLBEDecoder.java    | 20 ++++-
 .../tsfile/encoding/decoder/LongRLBEDecoder.java   | 20 ++++-
 .../tsfile/encoding/decoder/RLBEDecoderTest.java   | 91 ++++++++++++++++++++++
 5 files changed, 226 insertions(+), 4 deletions(-)

diff --git a/cpp/src/encoding/rlbe_decoder.h b/cpp/src/encoding/rlbe_decoder.h
index f4028bc83..20337fa55 100644
--- a/cpp/src/encoding/rlbe_decoder.h
+++ b/cpp/src/encoding/rlbe_decoder.h
@@ -112,8 +112,8 @@ class RLBEDecoder : public Decoder {
             return ret;
         }
         block_size_ = static_cast<int>(bits);
-        if (block_size_ < 0 || block_size_ > RLBE_BLOCK_DEFAULT_SIZE) {
-            return common::E_TSFILE_CORRUPTED;
+        if (block_size_ <= 0 || block_size_ > RLBE_BLOCK_DEFAULT_SIZE) {
+            return common::E_DECODE_ERR;
         }
         for (int i = 0; i < block_size_ * 2; ++i) {
             data_[i] = 0;
@@ -136,6 +136,9 @@ class RLBEDecoder : public Decoder {
                 return ret;
             }
             int segment_length = static_cast<int>(bits);
+            if (segment_length < 1 || segment_length > Traits::VALUE_BITS) {
+                return common::E_DECODE_ERR;
+            }
 
             int now = 0;
             int next = 0;
@@ -147,10 +150,23 @@ class RLBEDecoder : public Decoder {
             uint64_t run_length = 0;
             int j = 1;
             while (true) {
+                if (j >= static_cast<int>(sizeof(fibonacci_) /
+                                          sizeof(fibonacci_[0]))) {
+                    return common::E_DECODE_ERR;
+                }
                 if (j > 1) {
                     fibonacci_[j] = fibonacci_[j - 1] + fibonacci_[j - 2];
+                    if (fibonacci_[j] <= fibonacci_[j - 1]) {
+                        return common::E_DECODE_ERR;
+                    }
                 }
                 if (now == 1) {
+                    const uint64_t remaining =
+                        static_cast<uint64_t>(block_size_ - write_index_ - 1);
+                    if (fibonacci_[j] > remaining ||
+                        run_length > remaining - fibonacci_[j]) {
+                        return common::E_DECODE_ERR;
+                    }
                     run_length += fibonacci_[j];
                 }
                 if (now == 1 && next == 1) {
diff --git a/cpp/test/encoding/rlbe_codec_test.cc 
b/cpp/test/encoding/rlbe_codec_test.cc
index f8571dc99..2955f7f47 100644
--- a/cpp/test/encoding/rlbe_codec_test.cc
+++ b/cpp/test/encoding/rlbe_codec_test.cc
@@ -20,6 +20,7 @@
 #include <gtest/gtest.h>
 
 #include <cmath>
+#include <cstdint>
 #include <limits>
 #include <vector>
 
@@ -30,6 +31,23 @@
 
 namespace storage {
 
+namespace {
+
+void append_bits(std::vector<uint8_t>& bytes, int& bit_count, uint32_t value,
+                 int width) {
+    for (int i = width - 1; i >= 0; --i) {
+        if (bytes.empty() || bit_count == 8) {
+            bytes.push_back(0);
+            bit_count = 0;
+        }
+        bytes.back() =
+            static_cast<uint8_t>((bytes.back() << 1) | ((value >> i) & 1));
+        ++bit_count;
+    }
+}
+
+}  // namespace
+
 TEST(RLBECodecTest, Int32RoundTrip) {
     IntRLBEEncoder encoder;
     IntRLBEDecoder decoder;
@@ -157,4 +175,65 @@ TEST(RLBECodecTest, FactoryAllocatesRLBECodecs) {
     DecoderFactory::free(double_decoder);
 }
 
+TEST(RLBECodecTest, RejectsInvalidBlockSize) {
+    std::vector<uint8_t> bytes(4, 0);
+    common::ByteStream stream;
+    stream.wrap_from(reinterpret_cast<const char*>(bytes.data()), 
bytes.size());
+    IntRLBEDecoder decoder;
+    int32_t value = 0;
+    EXPECT_EQ(decoder.read_int32(value, stream), common::E_DECODE_ERR);
+}
+
+TEST(RLBECodecTest, RejectsRunLengthBeyondBlock) {
+    std::vector<uint8_t> bytes;
+    int bit_count = 0;
+    append_bits(bytes, bit_count, 1, 32);     // block size
+    append_bits(bytes, bit_count, 1, 6);      // segment length (int32 RLBE)
+    append_bits(bytes, bit_count, 0b011, 3);  // Fibonacci code for run length 
2
+    append_bits(bytes, bit_count, 0, 2);      // delta payload (not reached)
+    bytes.back() <<= (8 - bit_count);
+
+    common::ByteStream stream;
+    stream.wrap_from(reinterpret_cast<const char*>(bytes.data()), 
bytes.size());
+    IntRLBEDecoder decoder;
+    int32_t value = 0;
+    EXPECT_EQ(decoder.read_int32(value, stream), common::E_DECODE_ERR);
+}
+
+TEST(RLBECodecTest, RejectsInvalidIntSegmentLength) {
+    for (int segment_length : {0, 33}) {
+        std::vector<uint8_t> bytes;
+        int bit_count = 0;
+        append_bits(bytes, bit_count, 1, 32);  // block size
+        append_bits(bytes, bit_count, static_cast<uint32_t>(segment_length),
+                    6);  // segment length
+        bytes.back() <<= (8 - bit_count);
+
+        common::ByteStream stream;
+        stream.wrap_from(reinterpret_cast<const char*>(bytes.data()),
+                         bytes.size());
+        IntRLBEDecoder decoder;
+        int32_t value = 0;
+        EXPECT_EQ(decoder.read_int32(value, stream), common::E_DECODE_ERR);
+    }
+}
+
+TEST(RLBECodecTest, RejectsInvalidLongSegmentLength) {
+    for (int segment_length : {0, 65}) {
+        std::vector<uint8_t> bytes;
+        int bit_count = 0;
+        append_bits(bytes, bit_count, 1, 32);  // block size
+        append_bits(bytes, bit_count, static_cast<uint32_t>(segment_length),
+                    7);  // segment length
+        bytes.back() <<= (8 - bit_count);
+
+        common::ByteStream stream;
+        stream.wrap_from(reinterpret_cast<const char*>(bytes.data()),
+                         bytes.size());
+        LongRLBEDecoder decoder;
+        int64_t value = 0;
+        EXPECT_EQ(decoder.read_int64(value, stream), common::E_DECODE_ERR);
+    }
+}
+
 }  // namespace storage
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/encoding/decoder/IntRLBEDecoder.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/encoding/decoder/IntRLBEDecoder.java
index ae46d3e0e..bd046e313 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/encoding/decoder/IntRLBEDecoder.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/encoding/decoder/IntRLBEDecoder.java
@@ -19,11 +19,14 @@
 
 package org.apache.tsfile.encoding.decoder;
 
+import org.apache.tsfile.exception.encoding.TsFileDecodingException;
 import org.apache.tsfile.file.metadata.enums.TSEncoding;
 
 import java.nio.ByteBuffer;
 
 public class IntRLBEDecoder extends Decoder {
+  private static final int MAX_BLOCK_SIZE = 10000;
+
   /** constructor of IntRLBEDecoder */
   public IntRLBEDecoder() {
     super(TSEncoding.RLBE);
@@ -65,6 +68,9 @@ public class IntRLBEDecoder extends Decoder {
     readindex = -1;
     clearBuffer(buffer);
     readblocksize(buffer);
+    if (blocksize <= 0 || blocksize > MAX_BLOCK_SIZE) {
+      throw new TsFileDecodingException("Invalid RLBE block size: " + 
blocksize);
+    }
     data = new int[blocksize * 2 + 1];
     fibonacci = new int[blocksize * 2 + 1];
     for (int i = 0; i < blocksize * 2; i++) {
@@ -89,6 +95,9 @@ public class IntRLBEDecoder extends Decoder {
       for (int j = 5; j >= 0; j--) {
         seglength |= (readbit(buffer) << j);
       }
+      if (seglength < 1 || seglength > 32) {
+        throw new TsFileDecodingException("Invalid RLBE segment length: " + 
seglength);
+      }
 
       // generate repeat time of rle on delta
       int now = readbit(buffer);
@@ -96,8 +105,17 @@ public class IntRLBEDecoder extends Decoder {
 
       int j = 1;
       while (true) {
+        if (j >= fibonacci.length) {
+          throw new TsFileDecodingException("Invalid RLBE Fibonacci run 
length");
+        }
         if (j > 1) fibonacci[j] = fibonacci[j - 1] + fibonacci[j - 2];
-        if (now == 1) runlength += fibonacci[j];
+        if (now == 1) {
+          long candidate = (long) runlength + fibonacci[j];
+          if (candidate <= 0 || candidate > blocksize - writeindex - 1) {
+            throw new TsFileDecodingException("Invalid RLBE run length: " + 
candidate);
+          }
+          runlength = (int) candidate;
+        }
         // when now and next are both 1, the 1 of next is the symbol of ending 
of fibonacci code
         if (now == 1 && next == 1) break;
         j++;
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/encoding/decoder/LongRLBEDecoder.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/encoding/decoder/LongRLBEDecoder.java
index f41a5cd9b..6def046d8 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/encoding/decoder/LongRLBEDecoder.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/encoding/decoder/LongRLBEDecoder.java
@@ -19,11 +19,14 @@
 
 package org.apache.tsfile.encoding.decoder;
 
+import org.apache.tsfile.exception.encoding.TsFileDecodingException;
 import org.apache.tsfile.file.metadata.enums.TSEncoding;
 
 import java.nio.ByteBuffer;
 
 public class LongRLBEDecoder extends Decoder {
+  private static final int MAX_BLOCK_SIZE = 10000;
+
   /** constructor of LongRLBEDecoder */
   public LongRLBEDecoder() {
     super(TSEncoding.RLBE);
@@ -65,6 +68,9 @@ public class LongRLBEDecoder extends Decoder {
     readindex = -1;
     clearBuffer(buffer);
     readblocksize(buffer);
+    if (blocksize <= 0 || blocksize > MAX_BLOCK_SIZE) {
+      throw new TsFileDecodingException("Invalid RLBE block size: " + 
blocksize);
+    }
     data = new long[blocksize * 2 + 1];
     fibonacci = new long[blocksize * 2 + 1];
     for (int i = 0; i < blocksize * 2; i++) {
@@ -90,6 +96,9 @@ public class LongRLBEDecoder extends Decoder {
       for (int j = 6; j >= 0; j--) {
         seglength |= (readbit(buffer) << j);
       }
+      if (seglength < 1 || seglength > 64) {
+        throw new TsFileDecodingException("Invalid RLBE segment length: " + 
seglength);
+      }
 
       // generate repeat time of rle on delta
       int now = readbit(buffer);
@@ -97,8 +106,17 @@ public class LongRLBEDecoder extends Decoder {
 
       int j = 1;
       while (true) {
+        if (j >= fibonacci.length) {
+          throw new TsFileDecodingException("Invalid RLBE Fibonacci run 
length");
+        }
         if (j > 1) fibonacci[j] = fibonacci[j - 1] + fibonacci[j - 2];
-        if (now == 1) runlength += fibonacci[j];
+        if (now == 1) {
+          long value = fibonacci[j];
+          if (value <= 0 || runlength > blocksize - writeindex - 1 - value) {
+            throw new TsFileDecodingException("Invalid RLBE run length");
+          }
+          runlength += value;
+        }
         // when now and next are both 1, the 1 of next is the symbol of ending 
of fibonacci code
         if (now == 1 && next == 1) break;
         j++;
diff --git 
a/java/tsfile/src/test/java/org/apache/tsfile/encoding/decoder/RLBEDecoderTest.java
 
b/java/tsfile/src/test/java/org/apache/tsfile/encoding/decoder/RLBEDecoderTest.java
index bdb8082ee..db97cc25c 100644
--- 
a/java/tsfile/src/test/java/org/apache/tsfile/encoding/decoder/RLBEDecoderTest.java
+++ 
b/java/tsfile/src/test/java/org/apache/tsfile/encoding/decoder/RLBEDecoderTest.java
@@ -23,6 +23,7 @@ import org.apache.tsfile.encoding.encoder.Encoder;
 import org.apache.tsfile.encoding.encoder.FloatRLBE;
 import org.apache.tsfile.encoding.encoder.TSEncodingBuilder;
 import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.exception.encoding.TsFileDecodingException;
 import org.apache.tsfile.file.metadata.enums.TSEncoding;
 
 import org.junit.After;
@@ -258,6 +259,96 @@ public class RLBEDecoderTest {
     assertEquals(f, decoder.readDouble(buffer), doubleDelta);
   }
 
+  @Test
+  public void testRejectsInvalidBlockSize() {
+    ByteBuffer buffer = ByteBuffer.wrap(new byte[4]);
+    try {
+      new IntRLBEDecoder().readInt(buffer);
+      fail("Expected invalid RLBE block size to be rejected");
+    } catch (TsFileDecodingException expected) {
+      // expected
+    }
+  }
+
+  @Test
+  public void testRejectsRunLengthBeyondBlock() {
+    ByteArrayOutputStream output = new ByteArrayOutputStream();
+    BitWriter bits = new BitWriter(output);
+    bits.write(1, 32); // block size
+    bits.write(1, 6); // segment length
+    bits.write(0b011, 3); // Fibonacci code for run length 2, plus terminator
+    bits.write(0, 2); // delta payload (not reached after validation)
+    bits.flush();
+
+    try {
+      new IntRLBEDecoder().readInt(ByteBuffer.wrap(output.toByteArray()));
+      fail("Expected RLBE run length to be rejected");
+    } catch (TsFileDecodingException expected) {
+      // expected
+    }
+  }
+
+  @Test
+  public void testRejectsInvalidIntSegmentLength() {
+    for (int seglength : new int[] {0, 33}) {
+      ByteArrayOutputStream output = new ByteArrayOutputStream();
+      BitWriter bits = new BitWriter(output);
+      bits.write(1, 32); // block size
+      bits.write(seglength, 6); // invalid segment length
+      bits.flush();
+      try {
+        new IntRLBEDecoder().readInt(ByteBuffer.wrap(output.toByteArray()));
+        fail("Expected RLBE segment length to be rejected: " + seglength);
+      } catch (TsFileDecodingException expected) {
+        // expected
+      }
+    }
+  }
+
+  @Test
+  public void testRejectsInvalidLongSegmentLength() {
+    for (int seglength : new int[] {0, 65}) {
+      ByteArrayOutputStream output = new ByteArrayOutputStream();
+      BitWriter bits = new BitWriter(output);
+      bits.write(1, 32); // block size
+      bits.write(seglength, 7); // invalid segment length
+      bits.flush();
+      try {
+        new LongRLBEDecoder().readLong(ByteBuffer.wrap(output.toByteArray()));
+        fail("Expected RLBE segment length to be rejected: " + seglength);
+      } catch (TsFileDecodingException expected) {
+        // expected
+      }
+    }
+  }
+
+  private static class BitWriter {
+    private final ByteArrayOutputStream output;
+    private int currentByte;
+    private int bitCount;
+
+    private BitWriter(ByteArrayOutputStream output) {
+      this.output = output;
+    }
+
+    private void write(int value, int width) {
+      for (int i = width - 1; i >= 0; i--) {
+        currentByte = (currentByte << 1) | ((value >>> i) & 1);
+        if (++bitCount == 8) {
+          output.write(currentByte);
+          currentByte = 0;
+          bitCount = 0;
+        }
+      }
+    }
+
+    private void flush() {
+      if (bitCount > 0) {
+        output.write(currentByte << (8 - bitCount));
+      }
+    }
+  }
+
   private void testFloatLength(List<Float> valueList, boolean isDebug, int 
repeatCount)
       throws Exception {
     Encoder encoder = new FloatRLBE();

Reply via email to