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 03ce2f46 fix(parquet/encoding): reject invalid delta byte prefixes 
(#1014)
03ce2f46 is described below

commit 03ce2f462dab8fb3b45e4c18f38db36f26381127
Author: Minh Vu <[email protected]>
AuthorDate: Mon Jul 27 19:09:10 2026 +0200

    fix(parquet/encoding): reject invalid delta byte prefixes (#1014)
    
    ### Rationale for this change
    
    DELTA_BYTE_ARRAY prefix lengths are used directly as slice bounds.
    Negative prefixes or prefixes longer than the previous value therefore
    panic. The decoder also retains its previous value when reused for a new
    page, even though each page starts an independent delta sequence.
    
    ### What changes are included in this PR?
    
    - Reset the previous value in `SetData`.
    - Propagate prefix stream decode errors and short reads.
    - Require the first prefix to be zero and validate later prefixes before
    slicing.
    - Apply the same validation to decode and discard paths.
    
    ### Are these changes tested?
    
    Yes. Focused tests cover decoder reuse, negative prefixes, and prefixes
    longer than the previous value.
---
 parquet/internal/encoding/delta_byte_array.go      |  37 +++++++-
 .../encoding/delta_byte_array_validation_test.go   | 103 +++++++++++++++++++++
 2 files changed, 138 insertions(+), 2 deletions(-)

diff --git a/parquet/internal/encoding/delta_byte_array.go 
b/parquet/internal/encoding/delta_byte_array.go
index e377c9a4..8a5e9e00 100644
--- a/parquet/internal/encoding/delta_byte_array.go
+++ b/parquet/internal/encoding/delta_byte_array.go
@@ -18,6 +18,7 @@ package encoding
 
 import (
        "errors"
+       "fmt"
 
        "github.com/apache/arrow-go/v18/arrow/memory"
        "github.com/apache/arrow-go/v18/internal/utils"
@@ -163,6 +164,7 @@ func (d *DeltaByteArrayDecoder) Allocator() 
memory.Allocator { return d.mem }
 // SetData expects the passed in data to be the prefix lengths, followed by the
 // blocks of suffix data in order to initialize the decoder.
 func (d *DeltaByteArrayDecoder) SetData(nvalues int, data []byte) error {
+       d.lastVal = nil
        prefixLenDec := DeltaBitPackInt32Decoder{
                decoder: newDecoderBase(d.encoding, d.descr),
                mem:     d.mem,
@@ -171,15 +173,28 @@ func (d *DeltaByteArrayDecoder) SetData(nvalues int, data 
[]byte) error {
        if err := prefixLenDec.SetData(nvalues, data); err != nil {
                return err
        }
+       if nvalues < 0 || prefixLenDec.totalValues > uint64(nvalues) {
+               return fmt.Errorf("parquet: delta prefix count %d exceeds value 
count %d", prefixLenDec.totalValues, nvalues)
+       }
 
        d.prefixLengths = make([]int32, prefixLenDec.ValuesLeft())
        // decode all the prefix lengths first so we know how many bytes it 
took to get the
        // prefix lengths for nvalues
-       prefixLenDec.Decode(d.prefixLengths)
+       decoded, err := prefixLenDec.Decode(d.prefixLengths)
+       if err != nil {
+               return err
+       }
+       if decoded != len(d.prefixLengths) {
+               return errors.New("parquet: not enough delta byte array prefix 
lengths")
+       }
 
        // now that we know how many bytes we needed for the prefix lengths, 
the rest are the
        // delta length byte array encoding.
-       return d.DeltaLengthByteArrayDecoder.SetData(nvalues, 
data[int(prefixLenDec.bytesRead()):])
+       offset := prefixLenDec.bytesRead()
+       if offset < 0 || offset > int64(len(data)) {
+               return errors.New("parquet: invalid delta byte array suffix 
offset")
+       }
+       return d.DeltaLengthByteArrayDecoder.SetData(nvalues, data[offset:])
 }
 
 func (d *DeltaByteArrayDecoder) Discard(n int) (int, error) {
@@ -191,6 +206,9 @@ func (d *DeltaByteArrayDecoder) Discard(n int) (int, error) 
{
        remaining := n
        tmp := make([]parquet.ByteArray, 1)
        if d.lastVal == nil {
+               if len(d.prefixLengths) == 0 || d.prefixLengths[0] != 0 {
+                       return 0, errors.New("parquet: first delta byte array 
prefix length must be zero")
+               }
                if _, err := d.DeltaLengthByteArrayDecoder.Decode(tmp); err != 
nil {
                        return 0, err
                }
@@ -201,7 +219,13 @@ func (d *DeltaByteArrayDecoder) Discard(n int) (int, 
error) {
 
        var prefixLen int32
        for remaining > 0 {
+               if len(d.prefixLengths) == 0 {
+                       return n - remaining, errors.New("parquet: not enough 
delta byte array prefix lengths")
+               }
                prefixLen, d.prefixLengths = d.prefixLengths[0], 
d.prefixLengths[1:]
+               if prefixLen < 0 || int(prefixLen) > len(d.lastVal) {
+                       return n - remaining, fmt.Errorf("parquet: invalid 
delta byte array prefix length %d", prefixLen)
+               }
                prefix := d.lastVal[:prefixLen:prefixLen]
 
                if _, err := d.DeltaLengthByteArrayDecoder.Decode(tmp); err != 
nil {
@@ -231,6 +255,9 @@ func (d *DeltaByteArrayDecoder) Decode(out 
[]parquet.ByteArray) (int, error) {
 
        var err error
        if d.lastVal == nil {
+               if len(d.prefixLengths) == 0 || d.prefixLengths[0] != 0 {
+                       return 0, errors.New("parquet: first delta byte array 
prefix length must be zero")
+               }
                _, err = d.DeltaLengthByteArrayDecoder.Decode(out[:1])
                if err != nil {
                        return 0, err
@@ -243,7 +270,13 @@ func (d *DeltaByteArrayDecoder) Decode(out 
[]parquet.ByteArray) (int, error) {
        var prefixLen int32
        suffixHolder := make([]parquet.ByteArray, 1)
        for len(out) > 0 {
+               if len(d.prefixLengths) == 0 {
+                       return 0, errors.New("parquet: not enough delta byte 
array prefix lengths")
+               }
                prefixLen, d.prefixLengths = d.prefixLengths[0], 
d.prefixLengths[1:]
+               if prefixLen < 0 || int(prefixLen) > len(d.lastVal) {
+                       return 0, fmt.Errorf("parquet: invalid delta byte array 
prefix length %d", prefixLen)
+               }
 
                prefix := d.lastVal[:prefixLen:prefixLen]
                _, err = d.DeltaLengthByteArrayDecoder.Decode(suffixHolder)
diff --git a/parquet/internal/encoding/delta_byte_array_validation_test.go 
b/parquet/internal/encoding/delta_byte_array_validation_test.go
new file mode 100644
index 00000000..0a584835
--- /dev/null
+++ b/parquet/internal/encoding/delta_byte_array_validation_test.go
@@ -0,0 +1,103 @@
+// 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 encodeDeltaPrefixes(t *testing.T, values []int32) []byte {
+       t.Helper()
+       enc := NewEncoder(parquet.Types.Int32, 
parquet.Encodings.DeltaBinaryPacked, false, nil, memory.DefaultAllocator)
+       enc.(Int32Encoder).Put(values)
+       buf, err := enc.FlushValues()
+       require.NoError(t, err)
+       defer buf.Release()
+       return append([]byte(nil), buf.Bytes()...)
+}
+
+func encodeDeltaSuffixes(t *testing.T, values []parquet.ByteArray) []byte {
+       t.Helper()
+       enc := NewEncoder(parquet.Types.ByteArray, 
parquet.Encodings.DeltaLengthByteArray, false, nil, memory.DefaultAllocator)
+       enc.(ByteArrayEncoder).Put(values)
+       buf, err := enc.FlushValues()
+       require.NoError(t, err)
+       defer buf.Release()
+       return append([]byte(nil), buf.Bytes()...)
+}
+
+func deltaBytePage(t *testing.T, prefixes []int32, suffixes ...string) []byte {
+       t.Helper()
+       values := make([]parquet.ByteArray, len(suffixes))
+       for i := range suffixes {
+               values[i] = parquet.ByteArray(suffixes[i])
+       }
+       return append(encodeDeltaPrefixes(t, prefixes), encodeDeltaSuffixes(t, 
values)...)
+}
+
+func TestDeltaByteArrayDecoderResetsBetweenPages(t *testing.T) {
+       dec := NewDecoder(parquet.Types.ByteArray, 
parquet.Encodings.DeltaByteArray, nil, memory.DefaultAllocator)
+       require.NoError(t, dec.SetData(1, deltaBytePage(t, []int32{0}, "abc")))
+       _, err := dec.(ByteArrayDecoder).Decode(make([]parquet.ByteArray, 1))
+       require.NoError(t, err)
+
+       require.NoError(t, dec.SetData(1, deltaBytePage(t, []int32{1}, "x")))
+       _, err = dec.(ByteArrayDecoder).Decode(make([]parquet.ByteArray, 1))
+       require.Error(t, err)
+}
+
+func TestDeltaByteArrayDecoderRejectsInvalidPrefixes(t *testing.T) {
+       tests := []struct {
+               name     string
+               prefixes []int32
+       }{
+               {name: "nonzero first prefix", prefixes: []int32{1}},
+               {name: "negative prefix", prefixes: []int32{0, -1}},
+               {name: "prefix beyond previous value", prefixes: []int32{0, 2}},
+       }
+       operations := []struct {
+               name string
+               run  func(ByteArrayDecoder, int) error
+       }{
+               {name: "decode", run: func(dec ByteArrayDecoder, n int) error {
+                       _, err := dec.Decode(make([]parquet.ByteArray, n))
+                       return err
+               }},
+               {name: "discard", run: func(dec ByteArrayDecoder, n int) error {
+                       _, err := dec.Discard(n)
+                       return err
+               }},
+       }
+
+       for _, tt := range tests {
+               for _, op := range operations {
+                       t.Run(tt.name+"/"+op.name, func(t *testing.T) {
+                               suffixes := make([]string, len(tt.prefixes))
+                               for i := range suffixes {
+                                       suffixes[i] = "a"
+                               }
+                               dec := NewDecoder(parquet.Types.ByteArray, 
parquet.Encodings.DeltaByteArray, nil, memory.DefaultAllocator)
+                               require.NoError(t, 
dec.SetData(len(tt.prefixes), deltaBytePage(t, tt.prefixes, suffixes...)))
+                               require.Error(t, op.run(dec.(ByteArrayDecoder), 
len(tt.prefixes)))
+                       })
+               }
+       }
+}

Reply via email to