mirror of
https://github.com/hanzoai/wasm.git
synced 2026-10-30 15:07:30 +00:00
* Detect imported and defined Memory64 modules; name unsupported sandbox backend * Fail explicitly on Memory64 guests in 32-bit wazero sandbox * Test imported/defined Memory64 classification and explicit runtime rejection * Document explicit Memory64 sandbox contract and migration requirements * Clarify Memory64 capability and native-versus-sandbox boundary * CI: verify formatting, race safety and Memory64 rejection tests * Format Memory64 sandbox test for Go CI * Scope CI to self-contained sandbox tests; compiler fixtures require external binaries
175 lines
3.8 KiB
Go
175 lines
3.8 KiB
Go
// Copyright © 2026 Hanzo AI. MIT License.
|
|
|
|
package wasm
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
)
|
|
|
|
// ErrMemory64Unsupported means a guest declares a 64-bit-indexed linear
|
|
// memory. The current sandbox backend is wazero, which does not yet support
|
|
// the Memory64 proposal. This is a backend limit, NOT a limit of Go's native
|
|
// process address space and NOT a limit of the separate wasm2go AOT compiler.
|
|
//
|
|
// AOT-generated Go is native code and DOES NOT preserve wazero isolation.
|
|
// Do not silently convert an untrusted sandbox request into native execution.
|
|
var ErrMemory64Unsupported = errors.New("wasm: Memory64 guest requires a Memory64-capable sandbox backend; use hanzoai/wasm2go for trusted native AOT code, not as a sandbox replacement")
|
|
|
|
// usesMemory64 examines only the memory declarations in the wasm binary:
|
|
// section 5 (defined memories) and section 2 (imported memories).
|
|
//
|
|
// This is a non-validating feature probe, not a second wasm parser. A corrupt,
|
|
// truncated or non-wasm binary is left to wazero's validation. It never reads
|
|
// outside input bounds or allocates based on untrusted lengths.
|
|
func usesMemory64(b []byte) bool {
|
|
if len(b) < 8 || !bytes.Equal(b[:8], []byte{'\x00', 'a', 's', 'm', '\x01', 0, 0, 0}) {
|
|
return false
|
|
}
|
|
pos := 8
|
|
for pos < len(b) {
|
|
id := b[pos]
|
|
pos++
|
|
n, ok := readULEB(b, &pos)
|
|
if !ok || n > uint64(len(b)-pos) {
|
|
return false
|
|
}
|
|
end := pos + int(n)
|
|
section := b[pos:end]
|
|
pos = end
|
|
switch id {
|
|
case 2:
|
|
if importsMemory64(section) {
|
|
return true
|
|
}
|
|
case 5:
|
|
if memoriesMemory64(section) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func readULEB(b []byte, pos *int) (uint64, bool) {
|
|
var n uint64
|
|
for shift := uint(0); shift < 70; shift += 7 {
|
|
if *pos >= len(b) {
|
|
return 0, false
|
|
}
|
|
c := b[*pos]
|
|
*pos++
|
|
if shift == 63 && c > 1 {
|
|
return 0, false
|
|
}
|
|
if shift > 63 {
|
|
return 0, false
|
|
}
|
|
n |= uint64(c&0x7f) << shift
|
|
if c&0x80 == 0 {
|
|
return n, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func skipName(b []byte, p *int) bool {
|
|
n, ok := readULEB(b, p)
|
|
if !ok || n > uint64(len(b)-*p) {
|
|
return false
|
|
}
|
|
*p += int(n)
|
|
return true
|
|
}
|
|
|
|
// memoryLimits reads flags plus the LEB width appropriate to its memory.
|
|
// Table limits share the same layout but the table's table64 flag does not
|
|
// imply the guest has Memory64.
|
|
func memoryLimits(b []byte, p *int) (bool, bool) {
|
|
flags, ok := readULEB(b, p)
|
|
if !ok || flags > 7 {
|
|
return false, false
|
|
}
|
|
min, ok := readULEB(b, p)
|
|
_ = min
|
|
if !ok {
|
|
return false, false
|
|
}
|
|
if flags&1 != 0 {
|
|
if _, ok := readULEB(b, p); !ok {
|
|
return false, false
|
|
}
|
|
}
|
|
return flags&4 != 0, true
|
|
}
|
|
|
|
func memoriesMemory64(b []byte) bool {
|
|
p := 0
|
|
n, ok := readULEB(b, &p)
|
|
if !ok || n > uint64(len(b)) {
|
|
return false
|
|
}
|
|
for i := uint64(0); i < n; i++ {
|
|
is64, ok := memoryLimits(b, &p)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if is64 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func importsMemory64(b []byte) bool {
|
|
p := 0
|
|
n, ok := readULEB(b, &p)
|
|
if !ok || n > uint64(len(b)) {
|
|
return false
|
|
}
|
|
for i := uint64(0); i < n; i++ {
|
|
if !skipName(b, &p) || !skipName(b, &p) || p >= len(b) {
|
|
return false
|
|
}
|
|
kind := b[p]
|
|
p++
|
|
switch kind {
|
|
case 0: // imported function type index
|
|
if _, ok := readULEB(b, &p); !ok {
|
|
return false
|
|
}
|
|
case 1: // table reftype and limits
|
|
if p >= len(b) {
|
|
return false
|
|
}
|
|
p++
|
|
if _, ok := memoryLimits(b, &p); !ok {
|
|
return false
|
|
}
|
|
case 2: // imported memory
|
|
is64, ok := memoryLimits(b, &p)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if is64 {
|
|
return true
|
|
}
|
|
case 3: // global type + mutability
|
|
if len(b)-p < 2 {
|
|
return false
|
|
}
|
|
p += 2
|
|
case 4: // exception tag attribute + type index
|
|
if _, ok := readULEB(b, &p); !ok {
|
|
return false
|
|
}
|
|
if _, ok := readULEB(b, &p); !ok {
|
|
return false
|
|
}
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
return false
|
|
}
|