撰寫 Starnix 系統呼叫的評量標準

處理引數

系統呼叫應按照使用者空間提供的順序處理引數。舉例來說,引數應按照提供給系統呼叫的順序驗證。如果不同的系統呼叫會針對不同的驗證錯誤產生不同的 Errno 值,驗證順序就特別重要,因為如果引數有多種無效方式,我們就需要將正確的 Errno 值傳回給使用者空間。

如果系統呼叫引數需要讀取使用者空間記憶體 (例如,引數是 UserAddressUserRef),系統呼叫應使用引數,依引數提供的順序讀取使用者空間記憶體。我們從使用者空間記憶體讀取資料的順序會顯示給使用者空間,因為這些讀取作業可能會產生故障,並回報給使用者空間。

處理錯誤

使用者空間不應能使用系統呼叫,導致 Starnix 核心當機。舉例來說,系統呼叫不應使用會恐慌的 Rust API (例如 unwrapexpect),因為系統偵測到無效或非預期的參數。

旗標或選項

許多系統呼叫會採用位元欄位引數 (通常是 u32),其中包含系統呼叫的旗標或選項。建議您先檢查引數是否含有不明位元,再對引數進行更詳細的處理,藉此驗證這個位元欄位。

範例:

if flags & !(AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW) != 0 {
    track_stub!(...)
    return error!(EINVAL);
}

使用 track_stub 巨集追蹤不明標記。追蹤不支援的功能,有助於偵錯使用者空間程式碼的人員發現,他們遇到的問題可能是 Starnix 缺少功能所致。

Starnix 經常使用 bitflags 巨集,為以引數形式傳遞至系統呼叫的位元欄位定義 Starnix 內部型別。在大多數情況下,系統呼叫應使用 from_bits 方法將原始系統呼叫引數轉換為 Starnix 內部型別,因為 from_bits 可讓呼叫端明確處理原始值含有不明位元的情況。

對使用者空間提供的值執行算術運算

系統呼叫應避免對來自使用者空間的值執行算術運算,這可能會導致數值溢位。舉例來說,如果系統呼叫從使用者空間收到兩個數值引數,且使用者空間提供的值過大,則新增這些值 (未經過驗證) 可能會導致數值溢位。在 Rust 中,數值溢位會導致恐慌,這並非處理無效引數的正確方式。

請改為驗證數值範圍,或使用 checked_add 等函式,讓呼叫程式碼明確處理溢位。建議使用 UserValue,而非原始數值型別,方便驗證來自使用者空間的值。

使用者地址

處理使用者空間位址時,請勿使用原始數值或原始指標。請改用地址的 UserAddress 類型。如果位址是指向使用者空間中特定結構的指標,請使用 UserRef 型別,該型別會將特定結構做為型別參數。如果地址是指向結構體的指標,且該結構體在不同架構上的版面配置不同,請使用 MultiArchUserRef 型別。UserRefMultiArchUserRef 類型也適用於處理物件陣列。

系統呼叫不應從使用者空間記憶體讀取相同位置超過一次,因為使用者空間中的另一個執行緒可能會在系統呼叫執行時修改使用者空間記憶體。請改為從使用者空間複製到系統呼叫實作中的變數。

寫入使用者空間記憶體的系統呼叫必須謹慎處理,因為使用者空間可能會提供彼此重疊的位址。因此,系統呼叫不應交錯讀取和寫入使用者空間記憶體。因此,系統呼叫應先從使用者空間執行所有讀取作業,再執行寫入作業。