From 61dda5c9a56b021b5fde2b3aae2c703cced674ce Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Fri, 24 Jan 2025 16:53:31 +0700 Subject: [PATCH] Enhance memory management and validation in runtime - Added null checks for Region and memory data to prevent runtime errors. - Improved validation logic in Region.Validate to include checks for zero capacity and alignment. - Updated readMemory and writeMemory functions to ensure proper alignment and bounds checking. - Refactored input data handling in WazeroRuntime to validate and write environment, info, and message data with proper alignment. - Enhanced error reporting for memory operations and JSON validation to improve debugging capabilities. --- internal/runtime/memory.go | 278 +++++++++++++------------- internal/runtime/wazeroruntime.go | 311 +++++++++++++++++++----------- 2 files changed, 333 insertions(+), 256 deletions(-) diff --git a/internal/runtime/memory.go b/internal/runtime/memory.go index eed031d3..ff812654 100644 --- a/internal/runtime/memory.go +++ b/internal/runtime/memory.go @@ -73,6 +73,11 @@ func RegionFromBytes(data []byte, ok bool) (*Region, error) { // Validate checks if a Region is valid for the given memory size func (r *Region) Validate(memorySize uint32) error { + // Check for null region + if r == nil { + return fmt.Errorf("null region") + } + // Check alignment if r.Offset%alignmentSize != 0 { return fmt.Errorf("region offset %d not aligned to %d", r.Offset, alignmentSize) @@ -86,7 +91,7 @@ func (r *Region) Validate(memorySize uint32) error { return fmt.Errorf("region length %d exceeds capacity %d", r.Length, r.Capacity) } - // Check for overflow + // Check for overflow in offset + capacity calculation if r.Offset > math.MaxUint32-r.Capacity { return fmt.Errorf("region offset %d + capacity %d would overflow", r.Offset, r.Capacity) } @@ -96,6 +101,16 @@ func (r *Region) Validate(memorySize uint32) error { return fmt.Errorf("region end %d exceeds memory size %d", r.Offset+r.Capacity, memorySize) } + // Check minimum capacity + if r.Capacity == 0 { + return fmt.Errorf("region capacity cannot be zero") + } + + // Check first page boundary + if r.Offset < firstPageOffset { + return fmt.Errorf("region offset %d is below first page boundary %d", r.Offset, firstPageOffset) + } + return nil } @@ -107,12 +122,28 @@ type memoryManager struct { } func newMemoryManager(memory api.Memory, gasState *GasState) *memoryManager { - return &memoryManager{ + // Initialize memory with one page if empty + if memory.Size() == 0 { + if _, ok := memory.Grow(1); !ok { + panic("failed to initialize memory with one page") + } + } + + // Ensure memory size is valid + size := memory.Size() + if size > maxMemoryPages*wasmPageSize { + panic(fmt.Sprintf("memory size %d exceeds maximum allowed (%d pages)", size, maxMemoryPages)) + } + + // Initialize memory manager with proper alignment + mm := &memoryManager{ memory: memory, gasState: gasState, - nextOffset: firstPageOffset, - size: memory.Size(), + nextOffset: align(firstPageOffset, alignmentSize), + size: size, } + + return mm } // align ensures the offset meets CosmWasm alignment requirements @@ -131,15 +162,23 @@ func ensureAlignment(offset uint32, printDebug bool) uint32 { // readMemory is a helper to read bytes from memory with bounds checking func readMemory(memory api.Memory, offset uint32, length uint32) ([]byte, error) { + // Check for zero length + if length == 0 { + return nil, fmt.Errorf("zero length read") + } + + // Calculate aligned length + alignedLen := align(length, alignmentSize) + // Check for potential overflow in offset + length calculation - if offset > math.MaxUint32-length { - return nil, fmt.Errorf("memory access would overflow: offset=%d, length=%d", offset, length) + if offset > math.MaxUint32-alignedLen { + return nil, fmt.Errorf("memory access would overflow: offset=%d, length=%d", offset, alignedLen) } // Ensure we're not reading past memory bounds - if uint64(offset)+uint64(length) > uint64(memory.Size()) { + if uint64(offset)+uint64(alignedLen) > uint64(memory.Size()) { return nil, fmt.Errorf("read would exceed memory bounds: offset=%d, length=%d, memory_size=%d", - offset, length, memory.Size()) + offset, alignedLen, memory.Size()) } // Ensure offset is properly aligned @@ -147,24 +186,47 @@ func readMemory(memory api.Memory, offset uint32, length uint32) ([]byte, error) return nil, fmt.Errorf("unaligned memory read: offset=%d must be aligned to %d", offset, alignmentSize) } - data, ok := memory.Read(offset, length) + // Ensure offset is after first page + if offset < firstPageOffset { + return nil, fmt.Errorf("read offset %d is below first page boundary %d", offset, firstPageOffset) + } + + // Read aligned data + alignedData, ok := memory.Read(offset, alignedLen) if !ok { - return nil, fmt.Errorf("failed to read %d bytes from memory at offset %d", length, offset) + return nil, fmt.Errorf("failed to read %d bytes from memory at offset %d", alignedLen, offset) } + + // Validate data is not null + if alignedData == nil { + return nil, fmt.Errorf("null data read from memory") + } + + // Return only the requested length + data := alignedData[:length] + return data, nil } // writeMemory is a helper to write bytes to memory with bounds checking func writeMemory(memory api.Memory, offset uint32, data []byte, printDebug bool) error { + // Check for null data + if data == nil { + return fmt.Errorf("null data") + } + // Check for potential overflow in offset + length calculation - if offset > math.MaxUint32-uint32(len(data)) { - return fmt.Errorf("memory access would overflow: offset=%d, length=%d", offset, len(data)) + dataLen := uint32(len(data)) + alignedLen := align(dataLen, alignmentSize) + + if offset > math.MaxUint32-alignedLen { + return fmt.Errorf("memory access would overflow: offset=%d, length=%d", offset, alignedLen) } // Ensure we're not writing past memory bounds - if uint64(offset)+uint64(len(data)) > uint64(memory.Size()) { + if uint64(offset)+uint64(alignedLen) > uint64(memory.Size()) { return fmt.Errorf("write would exceed memory bounds: offset=%d, length=%d, memory_size=%d", - offset, len(data), memory.Size()) + offset, alignedLen, memory.Size()) } // Ensure the write is aligned @@ -172,16 +234,27 @@ func writeMemory(memory api.Memory, offset uint32, data []byte, printDebug bool) return fmt.Errorf("unaligned memory write: offset=%d must be aligned to %d", offset, alignmentSize) } + // Ensure offset is after first page + if offset < firstPageOffset { + return fmt.Errorf("write offset %d is below first page boundary %d", offset, firstPageOffset) + } + if printDebug { - fmt.Printf("[DEBUG] Writing %d bytes to memory at offset 0x%x\n", len(data), offset) + fmt.Printf("[DEBUG] Writing %d bytes to memory at offset 0x%x (aligned to %d)\n", dataLen, offset, alignedLen) if len(data) < 1024 { fmt.Printf("[DEBUG] Data: %s\n", string(data)) } } - if !memory.Write(offset, data) { - return fmt.Errorf("failed to write %d bytes to memory at offset %d", len(data), offset) + // Create aligned buffer and copy data + alignedData := make([]byte, alignedLen) + copy(alignedData, data) + + // Write aligned data to memory + if !memory.Write(offset, alignedData) { + return fmt.Errorf("failed to write %d bytes to memory at offset %d", alignedLen, offset) } + return nil } @@ -230,101 +303,33 @@ func (mm *memoryManager) ensureMemory(required uint32) error { return nil } -// writeAlignedData writes data to memory with proper alignment +// writeAlignedData writes data to memory with proper alignment and returns the write location and actual data length func (mm *memoryManager) writeAlignedData(data []byte, printDebug bool) (uint32, uint32, error) { - if len(data) == 0 { - return 0, 0, nil - } - - // If this looks like JSON data, validate it before writing - if len(data) > 0 && data[0] == '{' { - var js interface{} - if err := json.Unmarshal(data, &js); err != nil { - if printDebug { - fmt.Printf("[DEBUG] JSON validation failed: %v\n", err) - // Print the problematic section - errPos := 0 - if serr, ok := err.(*json.SyntaxError); ok { - errPos = int(serr.Offset) - } - start := errPos - 20 - if start < 0 { - start = 0 - } - end := errPos + 20 - if end > len(data) { - end = len(data) - } - fmt.Printf("[DEBUG] JSON error context: %q\n", string(data[start:end])) - fmt.Printf("[DEBUG] Full data: %s\n", hex.Dump(data)) - } - return 0, 0, fmt.Errorf("invalid JSON data: %w", err) - } - // Re-marshal to ensure consistent formatting - cleanData, err := json.Marshal(js) - if err != nil { - return 0, 0, fmt.Errorf("failed to re-marshal JSON: %w", err) - } - data = cleanData - } - - // Charge gas for the data - if err := mm.gasState.ConsumeMemory(uint32(len(data))); err != nil { - return 0, 0, err - } - - // Align the write location - writeOffset := ensureAlignment(mm.nextOffset, printDebug) - - // Calculate padded length for alignment - paddedLen := uint32(len(data)) - if paddedLen%alignmentSize != 0 { - paddedLen = ((paddedLen + alignmentSize - 1) / alignmentSize) * alignmentSize + // Check for null data + if data == nil { + return 0, 0, fmt.Errorf("null data") } - // Ensure enough memory with padding for alignment - if err := mm.ensureMemory(paddedLen); err != nil { - return 0, 0, err - } + // Calculate aligned length + dataLen := uint32(len(data)) + alignedLen := align(dataLen, alignmentSize) - // Write the data - if err := writeMemory(mm.memory, writeOffset, data, printDebug); err != nil { - return 0, 0, err + // Ensure we have enough memory + if mm.nextOffset > math.MaxUint32-alignedLen { + return 0, 0, fmt.Errorf("memory allocation would overflow: offset=%d, length=%d", mm.nextOffset, alignedLen) } - // Update next offset - mm.nextOffset = writeOffset + paddedLen - - // Return the write location and actual data length - return writeOffset, uint32(len(data)), nil -} - -// readResultRegion reads and validates a Region struct from memory -func readResultRegion(memory api.Memory, resultPtr uint32, printDebug bool) (*Region, error) { - // First read the Region struct - data, err := readMemory(memory, resultPtr, regionStructSize) - if err != nil { - return nil, fmt.Errorf("failed to read result region data at ptr=0x%x: %w", resultPtr, err) - } - - // Parse the Region struct - region, err := RegionFromBytes(data, true) - if err != nil { - return nil, fmt.Errorf("failed to parse region: %w", err) - } - - // Additional validation - if region.Offset >= memory.Size() { - return nil, fmt.Errorf("region offset out of bounds: offset=0x%x, memory_size=%d", - region.Offset, memory.Size()) + // Write data to memory + if err := writeMemory(mm.memory, mm.nextOffset, data, printDebug); err != nil { + return 0, 0, fmt.Errorf("failed to write data: %w", err) } - if region.Length > memory.Size()-region.Offset { - return nil, fmt.Errorf("region length exceeds memory bounds: offset=0x%x, length=%d, memory_size=%d", - region.Offset, region.Length, memory.Size()) - } + // Store current offset and update for next write + writeOffset := mm.nextOffset + mm.nextOffset += alignedLen - return region, nil + // Return write location and actual data length + return writeOffset, dataLen, nil } // prepareRegions allocates and prepares memory regions for input data @@ -406,28 +411,27 @@ func (mm *memoryManager) writeRegions(env, info, msg *Region) (uint32, uint32, u return envPtr, infoPtr, msgPtr, nil } -// readRegionData reads data from a Region with proper alignment and bounds checking +// readRegionData reads data from a Region with proper alignment and validation func readRegionData(memory api.Memory, region *Region, printDebug bool) ([]byte, error) { - // Ensure the read is aligned - region.Offset = ensureAlignment(region.Offset, printDebug) + // Validate region + if region == nil { + return nil, fmt.Errorf("null region") + } - // Read the data with bounds checking - data, err := readMemory(memory, region.Offset, region.Length) - if err != nil { - return nil, fmt.Errorf("failed to read region data: %w", err) + // Validate region fields + if region.Length > region.Capacity { + return nil, fmt.Errorf("region length %d exceeds capacity %d", region.Length, region.Capacity) } - if printDebug { - fmt.Printf("[DEBUG] Read region data (hex): % x\n", data) - if len(data) < 1024 { - fmt.Printf("[DEBUG] Read region data (string): %q\n", string(data)) - } + // Read data from memory + data, err := readMemory(memory, region.Offset, region.Length) + if err != nil { + return nil, fmt.Errorf("failed to read memory: %w", err) } // If this looks like JSON data, validate it if len(data) > 0 && data[0] == '{' { - // Try to validate JSON structure - var js map[string]interface{} + var js interface{} if err := json.Unmarshal(data, &js); err != nil { if printDebug { fmt.Printf("[DEBUG] JSON validation failed: %v\n", err) @@ -436,49 +440,29 @@ func readRegionData(memory api.Memory, region *Region, printDebug bool) ([]byte, if serr, ok := err.(*json.SyntaxError); ok { errPos = int(serr.Offset) } - start := errPos - 10 + start := errPos - 20 if start < 0 { start = 0 } - end := errPos + 10 + end := errPos + 20 if end > len(data) { end = len(data) } fmt.Printf("[DEBUG] JSON error context: %q\n", string(data[start:end])) + fmt.Printf("[DEBUG] Full data: %s\n", string(data)) } - // Don't return error - let caller handle invalid JSON if needed + return nil, fmt.Errorf("invalid JSON data: %w", err) } - } - - return data, nil -} - -// writeRegionData writes data to a Region with proper alignment and bounds checking -func writeRegionData(memory api.Memory, region *Region, data []byte, printDebug bool) error { - // Ensure the write is aligned - region.Offset = ensureAlignment(region.Offset, printDebug) - // Verify data fits within region capacity - if uint32(len(data)) > region.Capacity { - return fmt.Errorf("data length %d exceeds region capacity %d", len(data), region.Capacity) - } - - // Write the data - if err := writeMemory(memory, region.Offset, data, printDebug); err != nil { - return fmt.Errorf("failed to write region data: %w", err) - } - - // Update region length - region.Length = uint32(len(data)) - - if printDebug { - fmt.Printf("[DEBUG] Wrote region data (hex): % x\n", data) - if len(data) < 1024 { - fmt.Printf("[DEBUG] Wrote region data (string): %q\n", string(data)) + // Re-marshal to ensure consistent formatting + cleanData, err := json.Marshal(js) + if err != nil { + return nil, fmt.Errorf("failed to re-marshal JSON: %w", err) } + data = cleanData } - return nil + return data, nil } // writeToMemory writes data to memory and returns the offset where it was written diff --git a/internal/runtime/wazeroruntime.go b/internal/runtime/wazeroruntime.go index 851663b7..06e3ade8 100644 --- a/internal/runtime/wazeroruntime.go +++ b/internal/runtime/wazeroruntime.go @@ -441,20 +441,23 @@ func (w *WazeroRuntime) Instantiate(checksum []byte, env []byte, info []byte, ms if err != nil { return nil, types.GasReport{}, fmt.Errorf("failed to parse params: %w", err) } + + // Create gas state for tracking memory operations gasState := NewGasState(gasLimit) // Initialize runtime environment runtimeEnv := &RuntimeEnvironment{ - DB: store, - API: *api, - Querier: *querier, - Gas: *gasMeter, - gasLimit: gasLimit, - gasUsed: 0, - iterators: make(map[uint64]map[uint64]types.Iterator), + DB: store, + API: *api, + Querier: *querier, + Gas: *gasMeter, + gasLimit: gasLimit, + gasUsed: 0, + iterators: make(map[uint64]map[uint64]types.Iterator), + nextCallID: 1, } - // Create context with runtime environment + // Create context with environment ctx := context.WithValue(context.Background(), envKey, runtimeEnv) // Register host functions @@ -464,18 +467,15 @@ func (w *WazeroRuntime) Instantiate(checksum []byte, env []byte, info []byte, ms } defer hostModule.Close(ctx) - // Create module config and instantiate the environment module - moduleConfig := wazero.NewModuleConfig(). - WithName("env"). - WithStartFunctions() - - envModule, err := w.runtime.InstantiateModule(ctx, hostModule, moduleConfig) + // Create and instantiate environment module + envModule, err := w.runtime.InstantiateModule(ctx, hostModule, + wazero.NewModuleConfig().WithName("env").WithStartFunctions()) if err != nil { return nil, types.GasReport{}, fmt.Errorf("failed to instantiate env module: %w", err) } defer envModule.Close(ctx) - // Get the contract module from our cache + // Get the contract module w.mu.Lock() csHex := hex.EncodeToString(checksum) compiledModule, ok := w.compiledModules[csHex] @@ -485,45 +485,49 @@ func (w *WazeroRuntime) Instantiate(checksum []byte, env []byte, info []byte, ms } w.mu.Unlock() - // Create contract module config - contractConfig := wazero.NewModuleConfig(). - WithName("contract"). - WithStartFunctions() - - // Instantiate the contract module - contractModule, err := w.runtime.InstantiateModule(ctx, compiledModule, contractConfig) + // Create and instantiate contract module + contractModule, err := w.runtime.InstantiateModule(ctx, compiledModule, + wazero.NewModuleConfig().WithName("contract").WithStartFunctions()) if err != nil { return nil, types.GasReport{}, fmt.Errorf("failed to instantiate contract module: %w", err) } defer contractModule.Close(ctx) - // Initialize memory manager with gas state + // Get contract memory memory := contractModule.Memory() if memory == nil { return nil, types.GasReport{}, fmt.Errorf("contract module has no memory") } + + // Initialize memory with one page to avoid null pointer issues + if memory.Size() == 0 { + if _, ok := memory.Grow(1); !ok { + return nil, types.GasReport{}, fmt.Errorf("failed to initialize memory with one page") + } + } + + // Initialize memory manager memManager := newMemoryManager(memory, gasState) - // Prepare regions for input data - envRegion, infoRegion, msgRegion, err := memManager.prepareRegions(env, info, msg) - if err != nil { - return nil, types.GasReport{}, fmt.Errorf("failed to prepare regions: %w", err) + // Validate and prepare input data + if err := validateInputData(env, info, msg); err != nil { + return nil, types.GasReport{}, fmt.Errorf("invalid input data: %w", err) } - // Write the regions to memory and get their pointers - envPtr, infoPtr, msgPtr, err := memManager.writeRegions(envRegion, infoRegion, msgRegion) + // Write input data to memory + envPtr, infoPtr, msgPtr, err := writeInputData(memManager, env, info, msg, printDebug) if err != nil { - return nil, types.GasReport{}, fmt.Errorf("failed to write regions: %w", err) + return nil, types.GasReport{}, fmt.Errorf("failed to write input data: %w", err) } if printDebug { - fmt.Printf("Memory layout:\n") - fmt.Printf("env: offset=%d, size=%d, region_ptr=%d\n", envRegion.Offset, envRegion.Length, envPtr) - fmt.Printf("info: offset=%d, size=%d, region_ptr=%d\n", infoRegion.Offset, infoRegion.Length, infoPtr) - fmt.Printf("msg: offset=%d, size=%d, region_ptr=%d\n", msgRegion.Offset, msgRegion.Length, msgPtr) + fmt.Printf("Memory layout before instantiate:\n") + fmt.Printf("- env: ptr=0x%x, size=%d\n", envPtr, len(env)) + fmt.Printf("- info: ptr=0x%x, size=%d\n", infoPtr, len(info)) + fmt.Printf("- msg: ptr=0x%x, size=%d\n", msgPtr, len(msg)) } - // Get the instantiate function + // Get instantiate function instantiate := contractModule.ExportedFunction("instantiate") if instantiate == nil { return nil, types.GasReport{}, fmt.Errorf("instantiate function not exported") @@ -534,75 +538,150 @@ func (w *WazeroRuntime) Instantiate(checksum []byte, env []byte, info []byte, ms return nil, types.GasReport{}, err } - // Call the instantiate function + // Call instantiate function ret, err := instantiate.Call(ctx, uint64(envPtr), uint64(infoPtr), uint64(msgPtr)) if err != nil { return nil, types.GasReport{}, fmt.Errorf("instantiate call failed: %w", err) } - // Get the result using Region + // Validate return value if len(ret) != 1 { - return nil, types.GasReport{}, fmt.Errorf("expected 1 result, got %d", len(ret)) + return nil, types.GasReport{}, fmt.Errorf("expected 1 return value, got %d", len(ret)) } + // Read and validate result resultPtr := uint32(ret[0]) + result, err := readAndValidateResult(memory, resultPtr, printDebug) + if err != nil { + return nil, types.GasReport{}, fmt.Errorf("failed to read result: %w", err) + } + + // Create gas report + gasReport := types.GasReport{ + UsedInternally: runtimeEnv.gasUsed, + UsedExternally: gasState.GetGasUsed(), + Remaining: gasLimit - (runtimeEnv.gasUsed + gasState.GetGasUsed()), + Limit: gasLimit, + } + if printDebug { - fmt.Printf("[DEBUG] Result pointer: 0x%x\n", resultPtr) + fmt.Printf("Gas report:\n") + fmt.Printf("- Used internally: %d\n", gasReport.UsedInternally) + fmt.Printf("- Used externally: %d\n", gasReport.UsedExternally) + fmt.Printf("- Remaining: %d\n", gasReport.Remaining) + fmt.Printf("- Limit: %d\n", gasReport.Limit) } - // Ensure result pointer is aligned - alignedPtr := align(resultPtr, alignmentSize) - if alignedPtr != resultPtr { - if printDebug { - fmt.Printf("[DEBUG] Aligning result pointer from 0x%x to 0x%x\n", resultPtr, alignedPtr) - } - resultPtr = alignedPtr + return result, gasReport, nil +} + +// Helper functions for the Instantiate implementation + +func validateInputData(env, info, msg []byte) error { + if len(env) == 0 { + return fmt.Errorf("empty environment data") + } + if len(info) == 0 { + return fmt.Errorf("empty info data") + } + if len(msg) == 0 { + return fmt.Errorf("empty message data") } - data, ok := memory.Read(resultPtr, regionStructSize) - if !ok { - return nil, types.GasReport{}, fmt.Errorf("failed to read result region data") + // Validate env is proper JSON + var envData map[string]interface{} + if err := json.Unmarshal(env, &envData); err != nil { + return fmt.Errorf("invalid environment JSON: %w", err) } - if printDebug { - fmt.Printf("[DEBUG] Region data: %x\n", data) + + // Validate info is proper JSON + var infoData map[string]interface{} + if err := json.Unmarshal(info, &infoData); err != nil { + return fmt.Errorf("invalid info JSON: %w", err) } - resultRegion, err := RegionFromBytes(data, ok) + + // Validate msg is proper JSON + var msgData map[string]interface{} + if err := json.Unmarshal(msg, &msgData); err != nil { + return fmt.Errorf("invalid message JSON: %w", err) + } + + // Re-marshal each JSON to ensure consistent formatting + cleanEnv, err := json.Marshal(envData) if err != nil { - return nil, types.GasReport{}, fmt.Errorf("failed to parse result region: %w", err) + return fmt.Errorf("failed to re-marshal environment JSON: %w", err) } - if printDebug { - fmt.Printf("[DEBUG] Result region: offset=0x%x, capacity=%d, length=%d\n", - resultRegion.Offset, resultRegion.Capacity, resultRegion.Length) + env = cleanEnv + + cleanInfo, err := json.Marshal(infoData) + if err != nil { + return fmt.Errorf("failed to re-marshal info JSON: %w", err) } + info = cleanInfo - // Ensure data offset is aligned - alignedOffset := align(resultRegion.Offset, alignmentSize) - if alignedOffset != resultRegion.Offset { - if printDebug { - fmt.Printf("[DEBUG] Aligning data offset from 0x%x to 0x%x\n", resultRegion.Offset, alignedOffset) - } - resultRegion.Offset = alignedOffset + cleanMsg, err := json.Marshal(msgData) + if err != nil { + return fmt.Errorf("failed to re-marshal message JSON: %w", err) } + msg = cleanMsg - // Read result data - data, ok = memory.Read(resultRegion.Offset, resultRegion.Length) - if !ok { - return nil, types.GasReport{}, fmt.Errorf("failed to read result data") + return nil +} + +func writeInputData(mm *memoryManager, env, info, msg []byte, printDebug bool) (envPtr, infoPtr, msgPtr uint32, err error) { + // Write environment data + envPtr, _, err = mm.writeAlignedData(env, printDebug) + if err != nil { + return 0, 0, 0, fmt.Errorf("failed to write env data: %w", err) } - if printDebug { - fmt.Printf("[DEBUG] Result data: %s\n", string(data)) + // Write info data + infoPtr, _, err = mm.writeAlignedData(info, printDebug) + if err != nil { + return 0, 0, 0, fmt.Errorf("failed to write info data: %w", err) } - // Create gas report - gasReport := types.GasReport{ - UsedInternally: runtimeEnv.gasUsed, - UsedExternally: gasState.GetGasUsed(), - Remaining: gasLimit - (runtimeEnv.gasUsed + gasState.GetGasUsed()), - Limit: gasLimit, + // Write message data + msgPtr, _, err = mm.writeAlignedData(msg, printDebug) + if err != nil { + return 0, 0, 0, fmt.Errorf("failed to write msg data: %w", err) } - return data, gasReport, nil + return envPtr, infoPtr, msgPtr, nil +} + +func readAndValidateResult(memory api.Memory, resultPtr uint32, printDebug bool) ([]byte, error) { + // Validate result pointer + if resultPtr == 0 { + return nil, fmt.Errorf("null result pointer") + } + + // Read result region + resultRegion, err := readResultRegionInternal(memory, resultPtr, printDebug) + if err != nil { + return nil, fmt.Errorf("failed to read result region: %w", err) + } + + // Read result data + data, err := readRegionData(memory, resultRegion, printDebug) + if err != nil { + return nil, fmt.Errorf("failed to read result data: %w", err) + } + + // Validate result is proper JSON if it looks like JSON + if len(data) > 0 && data[0] == '{' { + var js map[string]interface{} + if err := json.Unmarshal(data, &js); err != nil { + return nil, fmt.Errorf("invalid result JSON: %w", err) + } + // Re-marshal to ensure consistent formatting + data, err = json.Marshal(js) + if err != nil { + return nil, fmt.Errorf("failed to re-marshal result JSON: %w", err) + } + } + + return data, nil } func (w *WazeroRuntime) Execute(checksum, env, info, msg []byte, otherParams ...interface{}) ([]byte, types.GasReport, error) { @@ -1152,10 +1231,12 @@ func (w *WazeroRuntime) readFunctionResult(memory api.Memory, resultPtr uint32, } // Ensure result pointer is aligned - resultPtr = ensureAlignment(resultPtr, printDebug) + if resultPtr%alignmentSize != 0 { + return nil, fmt.Errorf("unaligned result pointer: %d must be aligned to %d", resultPtr, alignmentSize) + } // Read and validate the result Region - resultRegion, err := readResultRegion(memory, resultPtr, printDebug) + resultRegion, err := readResultRegionInternal(memory, resultPtr, printDebug) if err != nil { return nil, fmt.Errorf("failed to read result region: %w", err) } @@ -1183,7 +1264,7 @@ func (w *WazeroRuntime) readFunctionResult(memory api.Memory, resultPtr uint32, // Validate JSON response if len(data) > 0 && data[0] == '{' { - var js map[string]interface{} + var js interface{} if err := json.Unmarshal(data, &js); err != nil { if printDebug { fmt.Printf("[DEBUG] JSON validation failed: %v\n", err) @@ -1192,39 +1273,26 @@ func (w *WazeroRuntime) readFunctionResult(memory api.Memory, resultPtr uint32, if serr, ok := err.(*json.SyntaxError); ok { errPos = int(serr.Offset) } - start := errPos - 10 + start := errPos - 20 if start < 0 { start = 0 } - end := errPos + 10 + end := errPos + 20 if end > len(data) { end = len(data) } fmt.Printf("[DEBUG] JSON error context: %q\n", string(data[start:end])) fmt.Printf("[DEBUG] Full data: %s\n", string(data)) } - // Try to repair common corruption patterns - repaired := repairJSONResponse(data, printDebug) - if repaired != nil { - if printDebug { - fmt.Printf("[DEBUG] Attempting to repair corrupted JSON\n") - fmt.Printf("Original: %s\n", string(data)) - fmt.Printf("Repaired: %s\n", string(repaired)) - } - // Validate repaired JSON - if err := json.Unmarshal(repaired, &js); err == nil { - data = repaired - } else if printDebug { - fmt.Printf("[DEBUG] Repair attempt failed: %v\n", err) - } - } - } else { - // Re-marshal to ensure consistent formatting - cleanData, err := json.Marshal(js) - if err == nil { - data = cleanData - } + return nil, fmt.Errorf("invalid JSON response: %w", err) + } + + // Re-marshal to ensure consistent formatting + cleanData, err := json.Marshal(js) + if err != nil { + return nil, fmt.Errorf("failed to re-marshal JSON response: %w", err) } + data = cleanData } if printDebug { @@ -1584,13 +1652,38 @@ func (w *WazeroRuntime) SimulateStoreCode(code []byte) ([]byte, error, bool) { return checksum[:], nil, false } -func (w *WazeroRuntime) getContractModule(checksum []byte) (wazero.CompiledModule, error) { - w.mu.Lock() - defer w.mu.Unlock() +func readResultRegionInternal(memory api.Memory, resultPtr uint32, printDebug bool) (*Region, error) { + // Validate result pointer + if resultPtr == 0 { + return nil, fmt.Errorf("null result pointer") + } - module, ok := w.compiledModules[hex.EncodeToString(checksum)] - if !ok { - return nil, fmt.Errorf("module not found for checksum %x", checksum) + // Ensure pointer is aligned + if resultPtr%alignmentSize != 0 { + return nil, fmt.Errorf("unaligned result pointer: %d must be aligned to %d", resultPtr, alignmentSize) + } + + // Read region data + regionData, err := readMemory(memory, resultPtr, regionStructSize) + if err != nil { + return nil, fmt.Errorf("failed to read region data: %w", err) + } + + // Parse region + region, err := RegionFromBytes(regionData, true) + if err != nil { + return nil, fmt.Errorf("invalid region data: %w", err) + } + + // Validate region against memory size + if err := region.Validate(memory.Size()); err != nil { + return nil, fmt.Errorf("invalid result region: %w", err) + } + + if printDebug { + fmt.Printf("[DEBUG] Result region: offset=0x%x, capacity=%d, length=%d\n", + region.Offset, region.Capacity, region.Length) } - return module, nil + + return region, nil }