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 fbf934fd fix(parquet/variant): validate compound value bounds (#1126)
fbf934fd is described below

commit fbf934fd7c1db26d5ed0bca543f1324e7ad74031
Author: Minh Vu <[email protected]>
AuthorDate: Thu Aug 27 18:59:31 2026 +0200

    fix(parquet/variant): validate compound value bounds (#1126)
    
    ### Rationale for this change
    
    NewWithMetadata accepts truncated arrays and objects because compound
    values are validated lazily. Accessing one of those values can then
    panic on a short offset table or an out-of-range child value.
    
    ### What changes are included in this PR?
    
    Validate compound headers, offset tables, monotonic offsets, and nested
    value boundaries before returning a value while keeping the existing
    compound accessors lazy.
    
    ### Are these changes tested?
    
    - `go test ./parquet/variant`
    
    ### Are there any user-facing changes?
    
    Malformed compound variant values now return an error during
    construction instead of panicking during access.
---
 parquet/variant/large_value_test.go       |  97 ++++++
 parquet/variant/utils.go                  |  20 +-
 parquet/variant/variant.go                | 514 ++++++++++++++++++++++++++----
 parquet/variant/variant_benchmark_test.go | 113 +++++++
 parquet/variant/variant_test.go           | 242 +++++++++++++-
 5 files changed, 900 insertions(+), 86 deletions(-)

diff --git a/parquet/variant/large_value_test.go 
b/parquet/variant/large_value_test.go
new file mode 100644
index 00000000..47f93169
--- /dev/null
+++ b/parquet/variant/large_value_test.go
@@ -0,0 +1,97 @@
+//go:build darwin || linux
+
+// 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 variant_test
+
+import (
+       "encoding/binary"
+       "strconv"
+       "syscall"
+       "testing"
+
+       "github.com/apache/arrow-go/v18/parquet/variant"
+       "github.com/stretchr/testify/require"
+)
+
+const maxEncodedOffset = uint64(1<<32 - 1)
+
+func mappedVariantValue(t *testing.T, dataStart uint64) []byte {
+       t.Helper()
+       if strconv.IntSize < 64 {
+               t.Skip("large variant values require 64-bit indexes")
+       }
+
+       value, err := syscall.Mmap(
+               -1,
+               0,
+               int(dataStart+maxEncodedOffset),
+               syscall.PROT_READ|syscall.PROT_WRITE,
+               syscall.MAP_ANON|syscall.MAP_PRIVATE,
+       )
+       require.NoError(t, err)
+       t.Cleanup(func() { require.NoError(t, syscall.Munmap(value)) })
+       return value
+}
+
+func appendMaxSizeBinary(value []byte, dataStart int) {
+       value[dataStart] = byte(variant.PrimitiveBinary << 2)
+       binary.LittleEndian.PutUint32(value[dataStart+1:], 
uint32(maxEncodedOffset-5))
+}
+
+func TestCompoundValueMayExceedUint32(t *testing.T) {
+       metaBytes := []byte{1, 1, 0, 1, 'a'}
+       meta, err := variant.NewMetadata(metaBytes)
+       require.NoError(t, err)
+
+       t.Run("array", func(t *testing.T) {
+               const dataStart = uint64(10)
+               value := mappedVariantValue(t, dataStart)
+               value[0] = byte(3<<2) | byte(variant.BasicArray)
+               value[1] = 1
+               binary.LittleEndian.PutUint32(value[2:6], 0)
+               binary.LittleEndian.PutUint32(value[6:10], 
uint32(maxEncodedOffset))
+               appendMaxSizeBinary(value, int(dataStart))
+
+               parsed, err := variant.NewWithMetadata(meta, value)
+               require.NoError(t, err)
+               array := parsed.Value().(variant.ArrayValue)
+               child, err := array.Value(0)
+               require.NoError(t, err)
+               require.Equal(t, variant.Binary, child.Type())
+               require.Len(t, child.Bytes(), int(maxEncodedOffset))
+       })
+
+       t.Run("object", func(t *testing.T) {
+               const dataStart = uint64(11)
+               value := mappedVariantValue(t, dataStart)
+               value[0] = byte(3<<2) | byte(variant.BasicObject)
+               value[1] = 1
+               value[2] = 0
+               binary.LittleEndian.PutUint32(value[3:7], 0)
+               binary.LittleEndian.PutUint32(value[7:11], 
uint32(maxEncodedOffset))
+               appendMaxSizeBinary(value, int(dataStart))
+
+               parsed, err := variant.NewWithMetadata(meta, value)
+               require.NoError(t, err)
+               object := parsed.Value().(variant.ObjectValue)
+               field, err := object.FieldAt(0)
+               require.NoError(t, err)
+               require.Equal(t, variant.Binary, field.Value.Type())
+               require.Len(t, field.Value.Bytes(), int(maxEncodedOffset))
+       })
+}
diff --git a/parquet/variant/utils.go b/parquet/variant/utils.go
index edeea2de..033dac93 100644
--- a/parquet/variant/utils.go
+++ b/parquet/variant/utils.go
@@ -120,13 +120,13 @@ func valueSize(v []byte) int {
                }
 
                sz := readLEU32(v[1 : 1+szBytes])
-               idSize, offsetSize := ((typeInfo>>2)&0b11)+1, 
uint32((typeInfo&0b11)+1)
-               idStart := 1 + szBytes
-               offsetStart := uint32(idStart) + sz*uint32(idSize)
-               dataStart := offsetStart + (sz+1)*offsetSize
+               idSize, offsetSize := ((typeInfo>>2)&0b11)+1, 
uint64((typeInfo&0b11)+1)
+               idStart := uint64(1 + szBytes)
+               offsetStart := idStart + uint64(sz)*uint64(idSize)
+               dataStart := offsetStart + (uint64(sz)+1)*offsetSize
 
-               idx := offsetStart + sz*uint32(offsetSize)
-               return int(dataStart + readLEU32(v[idx:idx+offsetSize]))
+               idx := offsetStart + uint64(sz)*offsetSize
+               return int(dataStart + uint64(readLEU32(v[idx:idx+offsetSize])))
        case byte(BasicArray):
                var szBytes uint8 = 1
                if ((typeInfo >> 2) & 0x1) != 0 {
@@ -134,11 +134,11 @@ func valueSize(v []byte) int {
                }
 
                sz := readLEU32(v[1 : 1+szBytes])
-               offsetSize, offsetStart := uint32((typeInfo&0b11)+1), 
uint32(1+szBytes)
-               dataStart := offsetStart + (sz+1)*offsetSize
+               offsetSize, offsetStart := uint64((typeInfo&0b11)+1), 
uint64(1+szBytes)
+               dataStart := offsetStart + (uint64(sz)+1)*offsetSize
 
-               idx := offsetStart + sz*uint32(offsetSize)
-               return int(dataStart + readLEU32(v[idx:idx+offsetSize]))
+               idx := offsetStart + uint64(sz)*offsetSize
+               return int(dataStart + uint64(readLEU32(v[idx:idx+offsetSize])))
        default:
                switch PrimitiveType(typeInfo) {
                case PrimitiveNull, PrimitiveBoolTrue, PrimitiveBoolFalse:
diff --git a/parquet/variant/variant.go b/parquet/variant/variant.go
index 70222234..a4557004 100644
--- a/parquet/variant/variant.go
+++ b/parquet/variant/variant.go
@@ -134,6 +134,7 @@ const (
        supportedVersion           = 1
        maxShortStringSize         = 0x3F
        metadataMaxSizeLimit       = 128 * 1024 * 1024 // 128MB
+       maxValidationDepth         = 1024              // bounds memory used 
while validating nested values.
 )
 
 var (
@@ -319,9 +320,9 @@ type ArrayValue struct {
        meta  Metadata
 
        numElements uint32
-       dataStart   uint32
+       dataStart   uint64
        offsetSize  uint8
-       offsetStart uint8
+       offsetStart uint64
 }
 
 // MarshalJSON implements the json.Marshaler interface for ArrayValue.
@@ -338,14 +339,10 @@ func (v ArrayValue) Len() uint32 { return v.numElements }
 func (v ArrayValue) Values() iter.Seq[Value] {
        return func(yield func(Value) bool) {
                for i := range v.numElements {
-                       idx := uint32(v.offsetStart) + i*uint32(v.offsetSize)
-                       offset := readLEU32(v.value[idx : 
idx+uint32(v.offsetSize)])
+                       idx := v.offsetStart + uint64(i)*uint64(v.offsetSize)
+                       offset := readLEU32(v.value[idx : 
idx+uint64(v.offsetSize)])
 
-                       val := v.value[v.dataStart+offset:]
-                       sz := valueSize(val)
-                       val = val[:sz] // trim to actual size
-
-                       if !yield(Value{value: val, meta: v.meta}) {
+                       if !yield(trimValue(v.meta, 
v.value[v.dataStart+uint64(offset):])) {
                                return
                        }
                }
@@ -360,10 +357,10 @@ func (v ArrayValue) Value(i uint32) (Value, error) {
                        arrow.ErrIndex, i, v.numElements)
        }
 
-       idx := uint32(v.offsetStart) + i*uint32(v.offsetSize)
-       offset := readLEU32(v.value[idx : idx+uint32(v.offsetSize)])
+       idx := v.offsetStart + uint64(i)*uint64(v.offsetSize)
+       offset := readLEU32(v.value[idx : idx+uint64(v.offsetSize)])
 
-       return Value{meta: v.meta, value: v.value[v.dataStart+offset:]}, nil
+       return trimValue(v.meta, v.value[v.dataStart+uint64(offset):]), nil
 }
 
 // ObjectValue represents an object (map/dictionary) of key-value pairs.
@@ -372,11 +369,11 @@ type ObjectValue struct {
        meta  Metadata
 
        numElements uint32
-       offsetStart uint32
-       dataStart   uint32
+       offsetStart uint64
+       dataStart   uint64
        idSize      uint8
        offsetSize  uint8
-       idStart     uint8
+       idStart     uint64
 }
 
 // ObjectField represents a key-value pair in an object.
@@ -398,18 +395,18 @@ func (v ObjectValue) ValueByKey(key string) (ObjectField, 
error) {
        const binarySearchThreshold = 32
        if n < binarySearchThreshold {
                for i := range n {
-                       idx := uint32(v.idStart) + i*uint32(v.idSize)
-                       id := readLEU32(v.value[idx : idx+uint32(v.idSize)])
+                       idx := v.idStart + uint64(i)*uint64(v.idSize)
+                       id := readLEU32(v.value[idx : idx+uint64(v.idSize)])
                        k, err := v.meta.KeyAt(id)
                        if err != nil {
                                return ObjectField{}, fmt.Errorf("invalid 
object value: fieldID at idx %d is not in metadata", idx)
                        }
                        if k == key {
-                               idx := uint32(v.offsetStart) + 
uint32(v.offsetSize)*i
-                               offset := readLEU32(v.value[idx : 
idx+uint32(v.offsetSize)])
+                               idx := v.offsetStart + 
uint64(v.offsetSize)*uint64(i)
+                               offset := readLEU32(v.value[idx : 
idx+uint64(v.offsetSize)])
                                return ObjectField{
                                        Key:   key,
-                                       Value: Value{value: 
v.value[v.dataStart+offset:], meta: v.meta}}, nil
+                                       Value: trimValue(v.meta, 
v.value[v.dataStart+uint64(offset):])}, nil
                        }
                }
                return ObjectField{}, arrow.ErrNotFound
@@ -418,8 +415,8 @@ func (v ObjectValue) ValueByKey(key string) (ObjectField, 
error) {
        i, j := uint32(0), n
        for i < j {
                mid := (i + j) >> 1
-               idx := uint32(v.idStart) + mid*uint32(v.idSize)
-               id := readLEU32(v.value[idx : idx+uint32(v.idSize)])
+               idx := v.idStart + uint64(mid)*uint64(v.idSize)
+               id := readLEU32(v.value[idx : idx+uint64(v.idSize)])
                k, err := v.meta.KeyAt(id)
                if err != nil {
                        return ObjectField{}, fmt.Errorf("invalid object value: 
fieldID at idx %d is not in metadata", idx)
@@ -429,12 +426,12 @@ func (v ObjectValue) ValueByKey(key string) (ObjectField, 
error) {
                case -1:
                        i = mid + 1
                case 0:
-                       idx := uint32(v.offsetStart) + uint32(v.offsetSize)*mid
-                       offset := readLEU32(v.value[idx : 
idx+uint32(v.offsetSize)])
+                       idx := v.offsetStart + uint64(v.offsetSize)*uint64(mid)
+                       offset := readLEU32(v.value[idx : 
idx+uint64(v.offsetSize)])
 
                        return ObjectField{
                                Key:   key,
-                               Value: Value{value: 
v.value[v.dataStart+offset:], meta: v.meta}}, nil
+                               Value: trimValue(v.meta, 
v.value[v.dataStart+uint64(offset):])}, nil
                case 1:
                        j = mid
                }
@@ -451,38 +448,36 @@ func (v ObjectValue) FieldAt(i uint32) (ObjectField, 
error) {
                        arrow.ErrIndex, i, v.numElements)
        }
 
-       idx := uint32(v.idStart) + i*uint32(v.idSize)
-       id := readLEU32(v.value[idx : idx+uint32(v.idSize)])
+       idx := v.idStart + uint64(i)*uint64(v.idSize)
+       id := readLEU32(v.value[idx : idx+uint64(v.idSize)])
        k, err := v.meta.KeyAt(id)
        if err != nil {
                return ObjectField{}, fmt.Errorf("invalid object value: fieldID 
at idx %d is not in metadata", idx)
        }
 
-       offsetIdx := uint32(v.offsetStart) + i*uint32(v.offsetSize)
-       offset := readLEU32(v.value[offsetIdx : offsetIdx+uint32(v.offsetSize)])
+       offsetIdx := v.offsetStart + uint64(i)*uint64(v.offsetSize)
+       offset := readLEU32(v.value[offsetIdx : offsetIdx+uint64(v.offsetSize)])
 
        return ObjectField{
                Key:   k,
-               Value: Value{value: v.value[v.dataStart+offset:], meta: 
v.meta}}, nil
+               Value: trimValue(v.meta, 
v.value[v.dataStart+uint64(offset):])}, nil
 }
 
 // Values returns an iterator over all key-value pairs in the object.
 func (v ObjectValue) Values() iter.Seq2[string, Value] {
        return func(yield func(string, Value) bool) {
                for i := range v.numElements {
-                       idx := uint32(v.idStart) + i*uint32(v.idSize)
-                       id := readLEU32(v.value[idx : idx+uint32(v.idSize)])
+                       idx := v.idStart + uint64(i)*uint64(v.idSize)
+                       id := readLEU32(v.value[idx : idx+uint64(v.idSize)])
                        k, err := v.meta.KeyAt(id)
                        if err != nil {
                                return
                        }
 
-                       offsetIdx := uint32(v.offsetStart) + 
i*uint32(v.offsetSize)
-                       offset := readLEU32(v.value[offsetIdx : 
offsetIdx+uint32(v.offsetSize)])
+                       offsetIdx := v.offsetStart + 
uint64(i)*uint64(v.offsetSize)
+                       offset := readLEU32(v.value[offsetIdx : 
offsetIdx+uint64(v.offsetSize)])
 
-                       value := v.value[v.dataStart+offset:]
-                       sz := valueSize(value)
-                       if !yield(k, Value{value: value[:sz], meta: v.meta}) {
+                       if !yield(k, trimValue(v.meta, 
v.value[v.dataStart+uint64(offset):])) {
                                return
                        }
                }
@@ -507,30 +502,34 @@ type Value struct {
        meta  Metadata
 }
 
+func trimValue(meta Metadata, value []byte) Value {
+       return Value{value: value[:valueSize(value)], meta: meta}
+}
+
 // NewWithMetadata creates a Value with the provided metadata and value bytes.
 func NewWithMetadata(meta Metadata, value []byte) (Value, error) {
        if len(value) == 0 {
                return Value{}, errors.New("invalid variant value: empty")
        }
-       if err := validateScalarValue(value); err != nil {
+       if err := validateValueBytes(meta, value); err != nil {
                return Value{}, err
        }
 
        return Value{value: value, meta: meta}, nil
 }
 
-func validateScalarValue(value []byte) error {
-       if basicTypeFromHeader(value[0]) == BasicShortString {
-               want := 1 + int(value[0]>>basicTypeBits)
-               if len(value) < want {
-                       return fmt.Errorf("invalid variant value: short string 
requires %d bytes, got %d", want, len(value))
-               }
-               return nil
+func validateValueBytes(meta Metadata, value []byte) error {
+       size, err := validateValue(meta, value)
+       if err != nil {
+               return err
        }
-       if basicTypeFromHeader(value[0]) != BasicPrimitive {
-               return nil
+       if size != len(value) {
+               return fmt.Errorf("invalid variant value: trailing bytes")
        }
+       return nil
+}
 
+func validatePrimitiveValue(value []byte) (int, error) {
        primitiveType := primitiveTypeFromHeader(value[0])
        want := 0
        switch primitiveType {
@@ -556,23 +555,406 @@ func validateScalarValue(value []byte) error {
                want = 17
        case PrimitiveBinary, PrimitiveString:
                if len(value) < 5 {
-                       return fmt.Errorf("invalid variant value: %s length 
prefix requires 5 bytes, got %d", primitiveType, len(value))
+                       return 0, fmt.Errorf("invalid variant value: %s length 
prefix requires 5 bytes, got %d", primitiveType, len(value))
                }
                dataLen := uint64(binary.LittleEndian.Uint32(value[1:5]))
                if dataLen > uint64(len(value)-5) {
-                       return fmt.Errorf("invalid variant value: %s data 
requires %d bytes, got %d", primitiveType, dataLen, len(value)-5)
+                       return 0, fmt.Errorf("invalid variant value: %s data 
requires %d bytes, got %d", primitiveType, dataLen, len(value)-5)
                }
-               return nil
+               return 5 + int(dataLen), nil
        default:
-               return fmt.Errorf("invalid variant value: unknown primitive 
type %d", primitiveType)
+               return 0, fmt.Errorf("invalid variant value: unknown primitive 
type %d", primitiveType)
        }
 
        if len(value) < want {
-               return fmt.Errorf("invalid variant value: %s requires %d bytes, 
got %d", primitiveType, want, len(value))
+               return 0, fmt.Errorf("invalid variant value: %s requires %d 
bytes, got %d", primitiveType, want, len(value))
+       }
+       return want, nil
+}
+
+type validationRange struct {
+       start uint64
+       end   uint64
+       field int
+}
+
+type validationFrame struct {
+       value               []byte
+       size                uint64
+       dataSize            uint32
+       dataStart           uint64
+       offsetStart         uint64
+       numChildren         uint32
+       nextChild           uint32
+       pendingIndex        uint32
+       pendingStart        uint32
+       pendingExpectedSize uint32
+       rangeStart          uint32
+       offsetSize          uint8
+       kind                uint8
+       initialized         bool
+}
+
+const (
+       validationStackInlineCapacity = 32
+       // Values that exceed the inline stack commonly need only a modest 
amount
+       // of additional depth, so avoid allocating the maximum stack for them.
+       validationStackIntermediateCapacity = 128
+       // The root value is at depth zero, so the stack needs one more frame 
than
+       // the maximum allowed nesting depth. Keeping this storage fixed 
prevents
+       // untrusted values from growing the validation stack on the heap.
+       validationStackCapacity       = maxValidationDepth + 1
+       validationRangeInlineCapacity = 64
+)
+
+// validateValue walks compound values with an explicit stack so valid values
+// do not consume the Go call stack. Nesting is bounded to keep validation
+// memory usage independent of attacker-controlled input depth.
+func validateValue(meta Metadata, value []byte) (int, error) {
+       var stackStorage [validationStackInlineCapacity]validationFrame
+       stack := stackStorage[:1]
+       stack[0].value = value
+
+       var rangeStorage [validationRangeInlineCapacity]validationRange
+       ranges := rangeStorage[:0]
+       return validateValueLoop(meta, value, stack, ranges, 0)
+}
+
+func validateValueLoop(meta Metadata, value []byte, stack []validationFrame, 
ranges []validationRange, rangeTop int) (int, error) {
+       var (
+               resultSize int
+               resultErr  error
+               hasResult  bool
+       )
+
+       for len(stack) > 0 {
+               frame := &stack[len(stack)-1]
+               if hasResult {
+                       hasResult = false
+
+                       if resultErr != nil {
+                               switch BasicType(frame.kind) {
+                               case BasicArray:
+                                       return 0, fmt.Errorf("invalid variant 
value: array element %d: %w", frame.pendingIndex, resultErr)
+                               case BasicObject:
+                                       return 0, fmt.Errorf("invalid variant 
value: object field %d: %w", frame.pendingIndex, resultErr)
+                               default:
+                                       return 0, resultErr
+                               }
+                       }
+
+                       switch BasicType(frame.kind) {
+                       case BasicArray:
+                               if uint64(resultSize) != 
uint64(frame.pendingExpectedSize) {
+                                       return 0, fmt.Errorf("invalid variant 
value: array element %d has trailing bytes", frame.pendingIndex)
+                               }
+                       case BasicObject:
+                               end := uint64(frame.pendingStart) + 
uint64(resultSize)
+                               if end > uint64(frame.dataSize) {
+                                       return 0, fmt.Errorf("invalid variant 
value: object field %d extends beyond data", frame.pendingIndex)
+                               }
+                               if rangeTop < len(ranges) {
+                                       ranges[rangeTop] = validationRange{
+                                               start: 
uint64(frame.pendingStart),
+                                               end:   end,
+                                               field: int(frame.pendingIndex),
+                                       }
+                               } else {
+                                       ranges = append(ranges, validationRange{
+                                               start: 
uint64(frame.pendingStart),
+                                               end:   end,
+                                               field: int(frame.pendingIndex),
+                                       })
+                               }
+                               rangeTop++
+                       }
+                       continue
+               }
+
+               if !frame.initialized {
+                       frame.initialized = true
+                       if err := prepareValidationFrame(meta, frame); err != 
nil {
+                               stack = stack[:len(stack)-1]
+                               if len(stack) == 0 {
+                                       return 0, err
+                               }
+                               resultErr = err
+                               hasResult = true
+                               continue
+                       }
+                       if BasicType(frame.kind) == BasicObject {
+                               frame.rangeStart = uint32(rangeTop)
+                       }
+               }
+
+               if frame.kind == uint8(BasicArray) || frame.kind == 
uint8(BasicObject) {
+                       if frame.nextChild < frame.numChildren {
+                               if len(stack) == validationStackCapacity {
+                                       return 0, fmt.Errorf("invalid variant 
value: maximum nesting depth exceeded")
+                               }
+                               if len(stack) == cap(stack) {
+                                       if cap(stack) == 
validationStackInlineCapacity {
+                                               return 
validateValueIntermediate(meta, value, stack, ranges, rangeTop)
+                                       }
+                                       return validateValueDeep(meta, value, 
stack, ranges, rangeTop)
+                               }
+
+                               child, index, start, expectedSize, err := 
nextValidationChild(frame)
+                               if err != nil {
+                                       stack = stack[:len(stack)-1]
+                                       if len(stack) == 0 {
+                                               return 0, err
+                                       }
+                                       resultErr = err
+                                       hasResult = true
+                                       continue
+                               }
+
+                               frame.nextChild++
+                               frame.pendingIndex = uint32(index)
+                               frame.pendingStart = uint32(start)
+                               frame.pendingExpectedSize = uint32(expectedSize)
+                               stack = append(stack, validationFrame{value: 
child})
+                               continue
+                       }
+
+                       if err := finishValidationFrame(frame, 
ranges[int(frame.rangeStart):rangeTop]); err != nil {
+                               if BasicType(frame.kind) == BasicObject {
+                                       rangeTop = int(frame.rangeStart)
+                               }
+                               stack = stack[:len(stack)-1]
+                               if len(stack) == 0 {
+                                       return 0, err
+                               }
+                               resultErr = err
+                               hasResult = true
+                               continue
+                       }
+                       if BasicType(frame.kind) == BasicObject {
+                               rangeTop = int(frame.rangeStart)
+                       }
+               }
+
+               resultSize = int(frame.size)
+               stack = stack[:len(stack)-1]
+               if len(stack) == 0 {
+                       return resultSize, nil
+               }
+               hasResult = true
+       }
+
+       return 0, errors.New("invalid variant value: validation stack 
exhausted")
+}
+
+func validateValueIntermediate(meta Metadata, value []byte, initialStack 
[]validationFrame, ranges []validationRange, rangeTop int) (int, error) {
+       var stackStorage [validationStackIntermediateCapacity]validationFrame
+       stack := stackStorage[:len(initialStack)]
+       copy(stack, initialStack)
+       return validateValueLoop(meta, value, stack, ranges, rangeTop)
+}
+
+func validateValueDeep(meta Metadata, value []byte, initialStack 
[]validationFrame, ranges []validationRange, rangeTop int) (int, error) {
+       var stackStorage [validationStackCapacity]validationFrame
+       stack := stackStorage[:len(initialStack)]
+       copy(stack, initialStack)
+       return validateValueLoop(meta, value, stack, ranges, rangeTop)
+}
+
+func finishValidationFrame(frame *validationFrame, ranges []validationRange) 
error {
+       if BasicType(frame.kind) != BasicObject {
+               return nil
+       }
+
+       slices.SortFunc(ranges, func(a, b validationRange) int {
+               switch {
+               case a.start < b.start:
+                       return -1
+               case a.start > b.start:
+                       return 1
+               default:
+                       return 0
+               }
+       })
+
+       var (
+               next          uint64
+               previousField int
+       )
+       for _, child := range ranges {
+               switch {
+               case child.start < next:
+                       return fmt.Errorf("invalid variant value: object fields 
%d and %d overlap", previousField, child.field)
+               case child.start > next:
+                       return fmt.Errorf("invalid variant value: object data 
has a gap before field %d", child.field)
+               }
+               next = child.end
+               previousField = child.field
+       }
+       if next != uint64(frame.dataSize) {
+               return fmt.Errorf("invalid variant value: object data has 
trailing bytes")
+       }
+       return nil
+}
+
+func prepareValidationFrame(meta Metadata, frame *validationFrame) error {
+       if len(frame.value) == 0 {
+               return errors.New("invalid variant value: empty")
+       }
+
+       frame.kind = uint8(basicTypeFromHeader(frame.value[0]))
+       switch BasicType(frame.kind) {
+       case BasicShortString:
+               want := 1 + int(frame.value[0]>>basicTypeBits)
+               if len(frame.value) < want {
+                       return fmt.Errorf("invalid variant value: short string 
requires %d bytes, got %d", want, len(frame.value))
+               }
+               frame.size = uint64(want)
+       case BasicObject:
+               return prepareObjectValidationFrame(meta, frame)
+       case BasicArray:
+               return prepareArrayValidationFrame(frame)
+       case BasicPrimitive:
+               size, err := validatePrimitiveValue(frame.value)
+               frame.size = uint64(size)
+               return err
+       default:
+               return fmt.Errorf("invalid variant value: unknown basic type 
%d", BasicType(frame.kind))
        }
        return nil
 }
 
+func prepareArrayValidationFrame(frame *validationFrame) error {
+       value := frame.value
+       typeInfo := value[0] >> basicTypeBits
+       offsetSize := uint8(typeInfo&0b11) + 1
+       isLarge := ((typeInfo >> 2) & 0x1) != 0
+
+       var (
+               numElements uint32
+               offsetStart uint64
+       )
+       if isLarge {
+               if len(value) < 5 {
+                       return fmt.Errorf("invalid variant value: array size 
requires 5 bytes, got %d", len(value))
+               }
+               numElements = readLEU32(value[1:5])
+               offsetStart = 5
+       } else {
+               if len(value) < 2 {
+                       return fmt.Errorf("invalid variant value: array size 
requires 2 bytes, got %d", len(value))
+               }
+               numElements = uint32(value[1])
+               offsetStart = 2
+       }
+
+       dataStart := offsetStart + (uint64(numElements)+1)*uint64(offsetSize)
+       if dataStart > uint64(len(value)) {
+               return fmt.Errorf("invalid variant value: array offset table 
ends at %d, got %d bytes", dataStart, len(value))
+       }
+
+       var previousOffset uint32
+       for i := uint64(0); i <= uint64(numElements); i++ {
+               pos := offsetStart + uint64(i)*uint64(offsetSize)
+               offset := readLEU32(value[int(pos) : int(pos)+int(offsetSize)])
+               if i == 0 && offset != 0 {
+                       return fmt.Errorf("invalid variant value: array first 
offset must be zero, got %d", offset)
+               }
+               if i > 0 && offset < previousOffset {
+                       return fmt.Errorf("invalid variant value: array offsets 
are not monotonic")
+               }
+               if dataStart+uint64(offset) > uint64(len(value)) {
+                       return fmt.Errorf("invalid variant value: array offset 
%d is out of range", offset)
+               }
+               previousOffset = offset
+       }
+
+       frame.dataStart = dataStart
+       frame.offsetStart = offsetStart
+       frame.offsetSize = offsetSize
+       frame.numChildren = numElements
+       frame.size = dataStart + uint64(previousOffset)
+       return nil
+}
+
+func prepareObjectValidationFrame(meta Metadata, frame *validationFrame) error 
{
+       value := frame.value
+       typeInfo := value[0] >> basicTypeBits
+       offsetSize := uint8(typeInfo&0b11) + 1
+       idSize := uint8((typeInfo>>2)&0b11) + 1
+       isLarge := ((typeInfo >> 4) & 0x1) != 0
+
+       var (
+               numElements uint32
+               elementSize uint64 = 1
+       )
+       if isLarge {
+               elementSize = 4
+       }
+       if uint64(len(value)) < 1+elementSize {
+               return fmt.Errorf("invalid variant value: object size requires 
%d bytes, got %d", 1+elementSize, len(value))
+       }
+       numElements = readLEU32(value[1 : 1+elementSize])
+
+       idStart := 1 + elementSize
+       offsetStart := idStart + uint64(numElements)*uint64(idSize)
+       dataStart := offsetStart + (uint64(numElements)+1)*uint64(offsetSize)
+       if dataStart > uint64(len(value)) {
+               return fmt.Errorf("invalid variant value: object offset table 
ends at %d, got %d bytes", dataStart, len(value))
+       }
+       finalOffsetPos := offsetStart + uint64(numElements)*uint64(offsetSize)
+       dataSize := readLEU32(value[int(finalOffsetPos) : 
int(finalOffsetPos)+int(offsetSize)])
+       if dataStart+uint64(dataSize) > uint64(len(value)) {
+               return fmt.Errorf("invalid variant value: object data ends at 
%d, got %d bytes", dataStart+uint64(dataSize), len(value))
+       }
+
+       var previousKey string
+       for i := range numElements {
+               idPos := idStart + uint64(i)*uint64(idSize)
+               id := readLEU32(value[int(idPos) : int(idPos)+int(idSize)])
+               key, err := meta.KeyAt(id)
+               if err != nil {
+                       return fmt.Errorf("invalid variant value: object field 
%d has invalid field ID %d: %w", i, id, err)
+               }
+               if i > 0 && strings.Compare(previousKey, key) >= 0 {
+                       return fmt.Errorf("invalid variant value: object field 
names are not strictly sorted at field %d", i)
+               }
+               previousKey = key
+
+               offsetPos := offsetStart + uint64(i)*uint64(offsetSize)
+               offset := readLEU32(value[int(offsetPos) : 
int(offsetPos)+int(offsetSize)])
+               if uint64(offset) > uint64(dataSize) {
+                       return fmt.Errorf("invalid variant value: object field 
%d offset %d is out of range", i, offset)
+               }
+       }
+
+       frame.dataStart = dataStart
+       frame.offsetStart = offsetStart
+       frame.offsetSize = offsetSize
+       frame.numChildren = numElements
+       frame.dataSize = dataSize
+       frame.size = dataStart + uint64(dataSize)
+       return nil
+}
+
+func nextValidationChild(frame *validationFrame) ([]byte, int, uint64, uint64, 
error) {
+       index := int(frame.nextChild)
+       position := frame.offsetStart + 
uint64(frame.nextChild)*uint64(frame.offsetSize)
+       offset := readLEU32(frame.value[int(position) : 
int(position)+int(frame.offsetSize)])
+       start := frame.dataStart + uint64(offset)
+
+       if BasicType(frame.kind) == BasicArray {
+               nextPosition := position + uint64(frame.offsetSize)
+               nextOffset := readLEU32(frame.value[int(nextPosition) : 
int(nextPosition)+int(frame.offsetSize)])
+               end := frame.dataStart + uint64(nextOffset)
+               return frame.value[int(start):int(end)], index, 0, end - start, 
nil
+       }
+
+       if uint64(offset) > uint64(frame.dataSize) {
+               return nil, index, 0, 0, fmt.Errorf("invalid variant value: 
object field %d offset %d is out of range", index, offset)
+       }
+       return frame.value[int(start):], index, uint64(offset), 0, nil
+}
+
 // New creates a Value by parsing both the metadata and value bytes.
 func New(meta, value []byte) (Value, error) {
        m, err := NewMetadata(meta)
@@ -755,11 +1137,11 @@ func (v Value) Value() any {
 
                debug.Assert(len(v.value) >= int(1+nelemSize), "invalid object 
value: too short")
                numElements := readLEU32(v.value[1 : 1+nelemSize])
-               idStart := uint32(1 + nelemSize)
-               offsetStart := idStart + numElements*uint32(fieldIdSz)
-               dataStart := offsetStart + (numElements+1)*uint32(fieldOffsetSz)
+               idStart := uint64(1 + nelemSize)
+               offsetStart := idStart + uint64(numElements)*uint64(fieldIdSz)
+               dataStart := offsetStart + 
(uint64(numElements)+1)*uint64(fieldOffsetSz)
 
-               debug.Assert(dataStart <= uint32(len(v.value)), "invalid object 
value: dataStart out of range")
+               debug.Assert(dataStart <= uint64(len(v.value)), "invalid object 
value: dataStart out of range")
                return ObjectValue{
                        value:       v.value,
                        meta:        v.meta,
@@ -768,7 +1150,7 @@ func (v Value) Value() any {
                        dataStart:   dataStart,
                        idSize:      fieldIdSz,
                        offsetSize:  fieldOffsetSz,
-                       idStart:     uint8(idStart),
+                       idStart:     idStart,
                }
        case BasicArray:
                valueHdr := (v.value[0] >> basicTypeBits)
@@ -776,25 +1158,25 @@ func (v Value) Value() any {
                isLarge := ((valueHdr >> 2) & 0b1) == 1
 
                var (
-                       sz                     int
-                       offsetStart, dataStart int
+                       sz          uint32
+                       offsetStart uint64
                )
 
                if isLarge {
-                       sz, offsetStart = int(readLEU32(v.value[1:5])), 5
+                       sz, offsetStart = readLEU32(v.value[1:5]), 5
                } else {
-                       sz, offsetStart = int(v.value[1]), 2
+                       sz, offsetStart = uint32(v.value[1]), 2
                }
 
-               dataStart = offsetStart + (sz+1)*int(fieldOffsetSz)
-               debug.Assert(dataStart <= len(v.value), "invalid array value: 
dataStart out of range")
+               dataStart := offsetStart + (uint64(sz)+1)*uint64(fieldOffsetSz)
+               debug.Assert(dataStart <= uint64(len(v.value)), "invalid array 
value: dataStart out of range")
                return ArrayValue{
                        value:       v.value,
                        meta:        v.meta,
-                       numElements: uint32(sz),
-                       dataStart:   uint32(dataStart),
+                       numElements: sz,
+                       dataStart:   dataStart,
                        offsetSize:  fieldOffsetSz,
-                       offsetStart: uint8(offsetStart),
+                       offsetStart: offsetStart,
                }
        }
 
diff --git a/parquet/variant/variant_benchmark_test.go 
b/parquet/variant/variant_benchmark_test.go
new file mode 100644
index 00000000..63fe3093
--- /dev/null
+++ b/parquet/variant/variant_benchmark_test.go
@@ -0,0 +1,113 @@
+// 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 variant
+
+import (
+       "fmt"
+       "testing"
+)
+
+func benchmarkNewWithMetadata(b *testing.B, value Value) {
+       b.Helper()
+       b.ReportAllocs()
+       b.ResetTimer()
+
+       for range b.N {
+               if _, err := NewWithMetadata(value.Metadata(), value.Bytes()); 
err != nil {
+                       b.Fatal(err)
+               }
+       }
+}
+
+func benchmarkObjectValue(b *testing.B, numFields int) Value {
+       b.Helper()
+
+       var builder Builder
+       start := builder.Offset()
+       fields := make([]FieldEntry, 0, numFields)
+       for i := range numFields {
+               key := fmt.Sprintf("field%02d", i)
+               fields = append(fields, builder.NextField(start, key))
+               if err := builder.AppendInt(int64(i)); err != nil {
+                       b.Fatal(err)
+               }
+       }
+       if err := builder.FinishObject(start, fields); err != nil {
+               b.Fatal(err)
+       }
+
+       value, err := builder.Build()
+       if err != nil {
+               b.Fatal(err)
+       }
+       return value
+}
+
+func benchmarkArrayValue(b *testing.B, numElements int) Value {
+       b.Helper()
+
+       var builder Builder
+       start := builder.Offset()
+       offsets := make([]int, 0, numElements)
+       for i := range numElements {
+               offsets = append(offsets, builder.NextElement(start))
+               if err := builder.AppendInt(int64(i)); err != nil {
+                       b.Fatal(err)
+               }
+       }
+       if err := builder.FinishArray(start, offsets); err != nil {
+               b.Fatal(err)
+       }
+
+       value, err := builder.Build()
+       if err != nil {
+               b.Fatal(err)
+       }
+       return value
+}
+
+func benchmarkNestedArrayValue(b *testing.B, depth int) Value {
+       b.Helper()
+
+       var nested any = int64(1)
+       for range depth {
+               nested = []any{nested}
+       }
+
+       var builder Builder
+       if err := builder.Append(nested); err != nil {
+               b.Fatal(err)
+       }
+
+       value, err := builder.Build()
+       if err != nil {
+               b.Fatal(err)
+       }
+       return value
+}
+
+func BenchmarkNewWithMetadataObject40Fields(b *testing.B) {
+       benchmarkNewWithMetadata(b, benchmarkObjectValue(b, 40))
+}
+
+func BenchmarkNewWithMetadataArray40Elements(b *testing.B) {
+       benchmarkNewWithMetadata(b, benchmarkArrayValue(b, 40))
+}
+
+func BenchmarkNewWithMetadataNestedArray1000(b *testing.B) {
+       benchmarkNewWithMetadata(b, benchmarkNestedArrayValue(b, 1000))
+}
diff --git a/parquet/variant/variant_test.go b/parquet/variant/variant_test.go
index 65d97000..89a5b03f 100644
--- a/parquet/variant/variant_test.go
+++ b/parquet/variant/variant_test.go
@@ -251,6 +251,30 @@ func primitiveHeader(p variant.PrimitiveType) uint8 {
        return (uint8(p) << 2)
 }
 
+func objectValueLayout(value []byte) (offsetStart, offsetSize, dataStart int) {
+       typeInfo := value[0] >> 2
+       numElements := int(value[1])
+       idSize := int((typeInfo>>2)&0b11) + 1
+       offsetSize = int(typeInfo&0b11) + 1
+       offsetStart = 2 + numElements*idSize
+       dataStart = offsetStart + (numElements+1)*offsetSize
+       return
+}
+
+func twoFieldObject(t *testing.T) variant.Value {
+       t.Helper()
+       var b variant.Builder
+       start := b.Offset()
+       fields := []variant.FieldEntry{b.NextField(start, "a")}
+       require.NoError(t, b.AppendInt(1))
+       fields = append(fields, b.NextField(start, "b"))
+       require.NoError(t, b.AppendInt(2))
+       require.NoError(t, b.FinishObject(start, fields))
+       v, err := b.Build()
+       require.NoError(t, err)
+       return v
+}
+
 func TestNullValue(t *testing.T) {
        emptyMeta := variant.EmptyMetadataBytes
        nullChars := []byte{primitiveHeader(variant.PrimitiveNull)}
@@ -580,6 +604,45 @@ func TestArrayValues(t *testing.T) {
        })
 }
 
+func TestIndexedValueRoundTrip(t *testing.T) {
+       assertRoundTrip := func(t *testing.T, value variant.Value) {
+               t.Helper()
+
+               roundTripped, err := variant.NewWithMetadata(value.Metadata(), 
value.Bytes())
+               require.NoError(t, err)
+               assert.Equal(t, value.Bytes(), roundTripped.Bytes())
+       }
+
+       t.Run("array values", func(t *testing.T) {
+               v, err := variant.ParseJSON(`[1, 2, {"nested": 3}]`, false)
+               require.NoError(t, err)
+
+               arr := v.Value().(variant.ArrayValue)
+               for i := range arr.Len() {
+                       value, err := arr.Value(i)
+                       require.NoError(t, err)
+                       assertRoundTrip(t, value)
+               }
+       })
+
+       t.Run("object values", func(t *testing.T) {
+               v, err := variant.ParseJSON(`{"a": 1, "b": [2, 3]}`, false)
+               require.NoError(t, err)
+
+               obj := v.Value().(variant.ObjectValue)
+               for _, key := range []string{"a", "b"} {
+                       field, err := obj.ValueByKey(key)
+                       require.NoError(t, err)
+                       assertRoundTrip(t, field.Value)
+               }
+               for i := range obj.NumElements() {
+                       field, err := obj.FieldAt(i)
+                       require.NoError(t, err)
+                       assertRoundTrip(t, field.Value)
+               }
+       })
+}
+
 func TestInvalidMetadata(t *testing.T) {
        tests := []struct {
                name     string
@@ -700,6 +763,173 @@ func TestInvalidPrimitiveValue(t *testing.T) {
        }
 }
 
+func TestInvalidCompoundValue(t *testing.T) {
+       for _, input := range []string{`[1,2]`, `{"value":1}`} {
+               t.Run(input, func(t *testing.T) {
+                       v, err := variant.ParseJSON(input, false)
+                       require.NoError(t, err)
+
+                       value := v.Bytes()
+                       _, err = variant.NewWithMetadata(v.Metadata(), 
value[:len(value)-1])
+                       require.Error(t, err)
+                       assert.Contains(t, err.Error(), "invalid variant value")
+               })
+       }
+}
+
+func TestValidateObjectPhysicalOffsets(t *testing.T) {
+       var b variant.Builder
+       start := b.Offset()
+       fields := []variant.FieldEntry{b.NextField(start, "c")}
+       require.NoError(t, b.AppendInt(3))
+       fields = append(fields, b.NextField(start, "a"))
+       require.NoError(t, b.AppendInt(1))
+       fields = append(fields, b.NextField(start, "b"))
+       require.NoError(t, b.AppendInt(2))
+       require.NoError(t, b.FinishObject(start, fields))
+       v, err := b.Build()
+       require.NoError(t, err)
+
+       // The fields are sorted by key, but their values were appended as c, 
a, b.
+       // This produces offsets in key order of 2, 4, 0, which is valid 
Variant data.
+       parsed, err := variant.NewWithMetadata(v.Metadata(), v.Bytes())
+       require.NoError(t, err)
+       obj := parsed.Value().(variant.ObjectValue)
+       for _, tt := range []struct {
+               key   string
+               value int8
+       }{
+               {key: "a", value: 1},
+               {key: "b", value: 2},
+               {key: "c", value: 3},
+       } {
+               field, err := obj.ValueByKey(tt.key)
+               require.NoError(t, err)
+               assert.Equal(t, tt.value, field.Value.Value())
+       }
+}
+
+func TestValidateObjectMetadataAndRanges(t *testing.T) {
+       base := twoFieldObject(t)
+       offsetStart, offsetSize, dataStart := objectValueLayout(base.Bytes())
+
+       t.Run("invalid field ID", func(t *testing.T) {
+               value := append([]byte(nil), base.Bytes()...)
+               value[2] = 0xff
+               _, err := variant.NewWithMetadata(base.Metadata(), value)
+               require.Error(t, err)
+               assert.Contains(t, err.Error(), "invalid field ID")
+       })
+
+       t.Run("unsorted field names", func(t *testing.T) {
+               value := append([]byte(nil), base.Bytes()...)
+               value[2], value[3] = value[3], value[2]
+               _, err := variant.NewWithMetadata(base.Metadata(), value)
+               require.Error(t, err)
+               assert.Contains(t, err.Error(), "not strictly sorted")
+       })
+
+       t.Run("duplicate field names", func(t *testing.T) {
+               value := append([]byte(nil), base.Bytes()...)
+               value[3] = value[2]
+               _, err := variant.NewWithMetadata(base.Metadata(), value)
+               require.Error(t, err)
+               assert.Contains(t, err.Error(), "not strictly sorted")
+       })
+
+       t.Run("overlapping ranges", func(t *testing.T) {
+               value := append([]byte(nil), base.Bytes()...)
+               value[offsetStart+offsetSize] = 0
+               _, err := variant.NewWithMetadata(base.Metadata(), value)
+               require.Error(t, err)
+               assert.Contains(t, err.Error(), "overlap")
+       })
+
+       t.Run("gap between ranges", func(t *testing.T) {
+               var b variant.Builder
+               start := b.Offset()
+               fields := []variant.FieldEntry{b.NextField(start, "a")}
+               require.NoError(t, b.AppendInt(1))
+               fields = append(fields, b.NextField(start, "b"))
+               require.NoError(t, b.AppendInt(2))
+               // Leave an otherwise valid value in the physical data region 
without
+               // assigning it to a field.
+               require.NoError(t, b.AppendInt(3))
+               require.NoError(t, b.FinishObject(start, fields))
+               v, err := b.Build()
+               require.NoError(t, err)
+
+               value := append([]byte(nil), v.Bytes()...)
+               offsetStart, offsetSize, _ := objectValueLayout(value)
+               value[offsetStart+offsetSize] = 4
+               _, err = variant.NewWithMetadata(v.Metadata(), value)
+               require.Error(t, err)
+               assert.Contains(t, err.Error(), "gap")
+       })
+
+       t.Run("offset into another value", func(t *testing.T) {
+               value := append([]byte(nil), base.Bytes()...)
+               value[offsetStart+offsetSize] = 1
+               _, err := variant.NewWithMetadata(base.Metadata(), value)
+               require.Error(t, err)
+               assert.Contains(t, err.Error(), "overlap")
+       })
+
+       t.Run("top-level trailing bytes", func(t *testing.T) {
+               value := append(append([]byte(nil), base.Bytes()...), 0)
+               _, err := variant.NewWithMetadata(base.Metadata(), value)
+               require.Error(t, err)
+               assert.Contains(t, err.Error(), "trailing bytes")
+       })
+
+       assert.Greater(t, dataStart, offsetStart)
+}
+
+func TestValidateDeeplyNestedValue(t *testing.T) {
+       var nested any = int64(1)
+       for range 1000 {
+               nested = []any{nested}
+       }
+
+       var b variant.Builder
+       require.NoError(t, b.Append(nested))
+       v, err := b.Build()
+       require.NoError(t, err)
+
+       _, err = variant.NewWithMetadata(v.Metadata(), v.Bytes())
+       require.NoError(t, err)
+}
+
+func TestRejectsExcessiveNesting(t *testing.T) {
+       var nested any = int64(1)
+       for range 1025 {
+               nested = []any{nested}
+       }
+
+       var b variant.Builder
+       require.NoError(t, b.Append(nested))
+       v, err := b.Build()
+       require.NoError(t, err)
+
+       _, err = variant.NewWithMetadata(v.Metadata(), v.Bytes())
+       require.Error(t, err)
+       assert.Contains(t, err.Error(), "maximum nesting depth exceeded")
+}
+
+func FuzzNewWithMetadataNeverPanics(f *testing.F) {
+       f.Add([]byte{byte(variant.PrimitiveNull << 2)})
+       f.Add([]byte{byte(variant.BasicShortString)})
+       f.Add([]byte{byte(variant.BasicArray), 0, 0})
+       f.Add([]byte{byte(variant.BasicObject), 0, 0})
+
+       meta, err := variant.NewMetadata(variant.EmptyMetadataBytes[:])
+       require.NoError(f, err)
+
+       f.Fuzz(func(t *testing.T, value []byte) {
+               _, _ = variant.NewWithMetadata(meta, value)
+       })
+}
+
 func TestInvalidObjectAccess(t *testing.T) {
        v := loadVariant(t, "object_primitive")
        obj := v.Value().(variant.ObjectValue)
@@ -723,17 +953,9 @@ func TestInvalidObjectAccess(t *testing.T) {
                // Set field ID to an invalid value
                corruptBytes[idPosition] = 0xFF
 
-               corrupt, err := variant.NewWithMetadata(v.Metadata(), 
corruptBytes)
-               require.NoError(t, err)
-
-               corruptObj := corrupt.Value().(variant.ObjectValue)
-               _, err = corruptObj.FieldAt(0)
-               require.Error(t, err)
-               assert.Contains(t, err.Error(), "fieldID")
-
-               _, err = corruptObj.ValueByKey("int_field")
+               _, err := variant.NewWithMetadata(v.Metadata(), corruptBytes)
                require.Error(t, err)
-               assert.Contains(t, err.Error(), "fieldID")
+               assert.Contains(t, err.Error(), "invalid field ID")
        })
 }
 

Reply via email to