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

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-go.git


The following commit(s) were added to refs/heads/main by this push:
     new d1c5c07b fix(parquet/encoding): respect declared RLE payload lengths 
(#1015)
d1c5c07b is described below

commit d1c5c07b7eeba34753f9419fd553b11445b6208b
Author: Minh Vu <[email protected]>
AuthorDate: Mon Jul 27 19:34:07 2026 +0200

    fix(parquet/encoding): respect declared RLE payload lengths (#1015)
    
    ### Rationale for this change
    
    RLE level and boolean decoders validate their declared byte length but
    then give the decoder every remaining byte in the page. A short declared
    payload can therefore consume bytes belonging to the next encoded
    section instead of reporting a truncated run.
    
    ### What changes are included in this PR?
    
    - Limit Data Page V1 level decoding to its four-byte-length-prefixed
    payload.
    - Limit Data Page V2 level decoding to its explicit byte count.
    - Apply the same boundary to RLE boolean values.
    - Validate the Data Page V2 length against the supplied data.
    
    ### Are these changes tested?
    
    Yes. Focused tests verify that bytes after each declared RLE interval
    are not consumed.
---
 parquet/internal/encoding/boolean_decoder.go       |  4 +-
 parquet/internal/encoding/levels.go                |  7 +--
 .../internal/encoding/rle_payload_length_test.go   | 57 ++++++++++++++++++++++
 3 files changed, 63 insertions(+), 5 deletions(-)

diff --git a/parquet/internal/encoding/boolean_decoder.go 
b/parquet/internal/encoding/boolean_decoder.go
index f46a8288..b644baf9 100644
--- a/parquet/internal/encoding/boolean_decoder.go
+++ b/parquet/internal/encoding/boolean_decoder.go
@@ -204,11 +204,11 @@ func (dec *RleBooleanDecoder) SetData(nvals int, data 
[]byte) error {
 
        // load the first 4 bytes in little-endian which indicates the length
        nbytes := binary.LittleEndian.Uint32(data[:4])
-       if nbytes > uint32(len(data)-4) {
+       if uint64(nbytes) > uint64(len(data)-4) {
                return fmt.Errorf("received invalid number of bytes - %d 
(corrupt data page?)", nbytes)
        }
 
-       dec.data = data[4:]
+       dec.data = data[4 : 4+int(nbytes)]
        if dec.rleDec == nil {
                dec.rleDec = utils.NewRleDecoder(bytes.NewReader(dec.data), 1)
        } else {
diff --git a/parquet/internal/encoding/levels.go 
b/parquet/internal/encoding/levels.go
index 86c9b84f..e88a18ef 100644
--- a/parquet/internal/encoding/levels.go
+++ b/parquet/internal/encoding/levels.go
@@ -198,11 +198,11 @@ func (l *LevelDecoder) SetData(encoding parquet.Encoding, 
maxLvl int16, nbuffere
                }
 
                nbytes := int32(binary.LittleEndian.Uint32(data[:4]))
-               if nbytes < 0 || nbytes > int32(len(data)-4) {
+               if nbytes < 0 || int64(nbytes) > int64(len(data)-4) {
                        return 0, errors.New("parquet: received invalid number 
of bytes (corrupt data page?)")
                }
 
-               buf := data[4:]
+               buf := data[4 : 4+int(nbytes)]
                if l.rle == nil {
                        l.rle = utils.NewRleDecoder(bytes.NewReader(buf), 
l.bitWidth)
                } else {
@@ -233,7 +233,7 @@ func (l *LevelDecoder) SetData(encoding parquet.Encoding, 
maxLvl int16, nbuffere
 // SetDataV2 is the same as SetData but only for DataPageV2 pages and only 
supports
 // run length encoding.
 func (l *LevelDecoder) SetDataV2(nbytes int32, maxLvl int16, nbuffered int, 
data []byte) error {
-       if nbytes < 0 {
+       if nbytes < 0 || int64(nbytes) > int64(len(data)) {
                return errors.New("parquet: invalid page header (corrupt data 
page?)")
        }
 
@@ -242,6 +242,7 @@ func (l *LevelDecoder) SetDataV2(nbytes int32, maxLvl 
int16, nbuffered int, data
        l.remaining = nbuffered
        l.bitWidth = bits.Len64(uint64(maxLvl))
 
+       data = data[:nbytes]
        if l.rle == nil {
                l.rle = utils.NewRleDecoder(bytes.NewReader(data), l.bitWidth)
        } else {
diff --git a/parquet/internal/encoding/rle_payload_length_test.go 
b/parquet/internal/encoding/rle_payload_length_test.go
new file mode 100644
index 00000000..ddcbc5c6
--- /dev/null
+++ b/parquet/internal/encoding/rle_payload_length_test.go
@@ -0,0 +1,57 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package encoding
+
+import (
+       "testing"
+
+       "github.com/apache/arrow-go/v18/arrow/memory"
+       "github.com/apache/arrow-go/v18/parquet"
+       "github.com/stretchr/testify/require"
+)
+
+func TestLevelDecoderStopsAtDeclaredRLELength(t *testing.T) {
+       // The declared payload contains the run header but not its value byte.
+       data := []byte{1, 0, 0, 0, 2, 1}
+       var dec LevelDecoder
+       _, err := dec.SetData(parquet.Encodings.RLE, 1, 1, data)
+       require.NoError(t, err)
+
+       _, _, err = dec.Decode(make([]int16, 1))
+       require.Error(t, err)
+}
+
+func TestLevelDecoderV2StopsAtDeclaredRLELength(t *testing.T) {
+       var dec LevelDecoder
+       require.NoError(t, dec.SetDataV2(1, 1, 1, []byte{2, 1}))
+
+       _, _, err := dec.Decode(make([]int16, 1))
+       require.Error(t, err)
+}
+
+func TestLevelDecoderV2RejectsOversizedDeclaredLength(t *testing.T) {
+       var dec LevelDecoder
+       require.Error(t, dec.SetDataV2(3, 1, 1, []byte{2, 1}))
+}
+
+func TestRLEBooleanDecoderStopsAtDeclaredLength(t *testing.T) {
+       dec := NewDecoder(parquet.Types.Boolean, parquet.Encodings.RLE, nil, 
memory.DefaultAllocator)
+       require.NoError(t, dec.SetData(1, []byte{1, 0, 0, 0, 2, 1}))
+
+       _, err := dec.(BooleanDecoder).Decode(make([]bool, 1))
+       require.Error(t, err)
+}

Reply via email to