zeroshade commented on code in PR #1126:
URL: https://github.com/apache/arrow-go/pull/1126#discussion_r3865629466


##########
parquet/variant/variant.go:
##########
@@ -556,23 +555,376 @@ 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
+       kind                BasicType
+       size                int
+       dataSize            uint64
+       dataStart           uint64
+       offsetStart         uint64
+       offsetSize          uint8
+       numChildren         uint32
+       nextChild           uint32
+       pendingIndex        int
+       pendingStart        uint64
+       pendingExpectedSize uint64
+       rangeStart          int
+       initialized         bool
+       compound            bool
+}
+
+const (
+       validationStackInlineCapacity = 32
+       validationRangeInlineCapacity = 64
+)
+
+// validateValue walks compound values with an explicit stack so valid values
+// are not limited by the Go call stack or an implementation-defined nesting
+// 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]
+       rangeTop := 0
+
+       var (
+               resultSize int
+               resultErr  error
+               hasResult  bool
+       )
+
+       for len(stack) > 0 {
+               frame := &stack[len(stack)-1]
+               if hasResult {
+                       hasResult = false
+
+                       if resultErr != nil {
+                               switch 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 frame.kind {
+                       case BasicArray:
+                               if uint64(resultSize) != 
frame.pendingExpectedSize {
+                                       return 0, fmt.Errorf("invalid variant 
value: array element %d has trailing bytes", frame.pendingIndex)
+                               }
+                       case BasicObject:
+                               end := frame.pendingStart + uint64(resultSize)
+                               if end > 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: frame.pendingStart,
+                                               end:   end,
+                                               field: frame.pendingIndex,
+                                       }
+                               } else {
+                                       ranges = append(ranges, validationRange{
+                                               start: frame.pendingStart,
+                                               end:   end,
+                                               field: 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 frame.kind == BasicObject {
+                               frame.rangeStart = rangeTop
+                       }
+               }
+
+               if frame.compound {
+                       if frame.nextChild < frame.numChildren {
+                               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 = index
+                               frame.pendingStart = start
+                               frame.pendingExpectedSize = expectedSize
+                               stack = append(stack, validationFrame{value: 
child})

Review Comment:
   **Blocking:** The common 40-child case is now allocation-free, but 
attacker-controlled nesting still grows this 120-byte frame stack with repeated 
slice reallocations. A valid 1,000-level nested array occupies 5,873 encoded 
bytes yet `NewWithMetadata` allocates about 393 KB; at 10,000 levels validation 
allocates about 6.2 MB. This leaves the prior deep-input 
memory-amplification/OOM concern unresolved on the row-read path. Please reduce 
or bound depth-dependent state and add a benchmark beyond 
`validationStackInlineCapacity`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to