系統程式設計的前沿

線性記憶體(linear memory)

當一個 Wasm 模組需要記憶體——用來存放字串、陣列、資料結構——那塊記憶體住在哪裡,又為什麼不會不小心塗改到主機?答案就是線性記憶體:一個單一、連續、可依位元組定址的位元組陣列,它只屬於該模組,別無其他。客體把自己的記憶體看成一整塊從偏移量 0 開始的平坦區塊,每一次載入與儲存都是對這塊區塊的索引。

具體來說,線性記憶體就是一個以 64 KiB 分頁為單位、可調整大小的大位元組陣列,由執行環境配置在客體碰不到的地方,再交給模組一扇朝它開的窗。Wasm 位址不是真正的機器指標——它是一個偏移量,一個像 0x1040 這樣的普通整數,指向這個陣列。當模組對偏移量 0x1040 執行 i32.load,執行環境會檢查 0x1040(以及其後的四個位元組)是否落在目前的記憶體大小之內;若是,就讀取;若否,就陷阱(trap),而非碰到其他任何東西。模組可以用 memory.grow 要求擴大記憶體,而主機自己的指標、程式碼與其他模組根本不是這個陣列的一部分,所以客體連稱呼它們的名字都沒有。這種「依建構而生的邊界檢查」就是整個訣竅:沙箱內的越界是一次乾淨的陷阱,絕不會變成一次對主機記憶體的讀取。

它之所以重要,是因為它正是「Wasm 受沙箱保護」背後的具體機制:隔離不是事後外掛的承諾,而是自然落在「客體只能定址自己的線性記憶體」這個事實上。但要精確看待它的極限。Wasm 相對於主機是記憶體安全的,相對於客體內部則未必:一個編譯成 Wasm 的 C 程式仍可能有經典的緩衝區溢位,破壞它自己的線性記憶體與自己的資料結構——它只是無法逃進主機。所以「沒有記憶體安全臭蟲」是錯的;「無法經由記憶體逃出沙箱」才是真正、較狹義的保證。

i32.const 0x1040 ;; 一個位址 = 指向「本」模組位元組陣列的普通偏移量 i32.load ;; 執行環境檢查 0x1040..0x1043 是否在界內,否則陷阱 ;; 主機真正的記憶體在這個陣列裡根本沒有任何位址

一次 Wasm 載入是對模組自有位元組陣列的、經邊界檢查的索引。越界就陷阱;它永遠碰不到主機記憶體。

線性記憶體保護的是「主機免受客體侵害」,而非「客體免受自己侵害」。Wasm 內部的緩衝區溢位破壞的是模組自己的線性記憶體,跟原生情況一模一樣;保證在於這種破壞會留在沙箱之內,無法變成對主機記憶體的讀或寫。

又稱
Wasm linear memorythe Wasm heapWasm 記憶體