From 4aa3ea71b1af725207898ff4191129c16fde77b6 Mon Sep 17 00:00:00 2001 From: Clark McCauley Date: Tue, 3 Dec 2024 16:26:51 -0700 Subject: [PATCH 1/5] Check for host function errors after calls --- derive/src/lib.rs | 2 ++ src/error.rs | 16 ++++++++++++++++ src/extism.rs | 1 + src/lib.rs | 4 ++++ 4 files changed, 23 insertions(+) create mode 100644 src/error.rs diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 5017d10..e1b8db2 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -330,6 +330,7 @@ pub fn host_fn( #vis unsafe fn #name #generics (#original_inputs) -> Result<#output, extism_pdk::Error> { let res = extism_pdk::Memory::from(#impl_name(#(#into_inputs),*)); + extism_pdk::get_host_func_error()?; <#output as extism_pdk::FromBytes>::from_bytes(&res.to_vec()) } }; @@ -341,6 +342,7 @@ pub fn host_fn( #vis unsafe fn #name #generics (#original_inputs) -> Result<#output, extism_pdk::Error> { let res = #impl_name(#(#into_inputs),*); + extism_pdk::get_host_func_error()?; Ok(res) } }; diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..f8520f2 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,16 @@ +use crate::*; + +/// Checks to see if the most recent host function call returned an error. +pub fn get_host_func_error() -> Result<(), Error> { + let offset = unsafe { extism::host_func_get_error() }; + if offset == 0 { + return Ok(()); + } + match Memory::find(offset) + .map(|mem| mem.to_string().ok()) + .flatten() + { + None => Ok(()), + Some(mem) => Err(Error::msg(mem.to_string())), + } +} diff --git a/src/extism.rs b/src/extism.rs index bf7674c..973fd3b 100644 --- a/src/extism.rs +++ b/src/extism.rs @@ -25,6 +25,7 @@ extern "C" { pub fn log_error(offs: u64); pub fn log_trace(offs: u64); pub fn get_log_level() -> i32; + pub fn host_func_get_error() -> u64; } /// Loads a byte array from Extism's memory. Only use this if you diff --git a/src/lib.rs b/src/lib.rs index 2cd9cee..a464789 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,10 @@ pub mod var; /// Types and functions for making HTTP requests pub mod http; +/// Functions and utilities for working with host function errors +mod error; +pub use error::get_host_func_error; + pub use anyhow::Error; pub use extism_convert::*; pub use extism_convert::{FromBytes, FromBytesOwned, ToBytes}; From e9b0ac7a237dd95910d8ac4ad9dc053f214cb715 Mon Sep 17 00:00:00 2001 From: Clark McCauley Date: Tue, 3 Dec 2024 17:02:12 -0700 Subject: [PATCH 2/5] Use existing `error_get` function to check for host function errors --- src/error.rs | 2 +- src/extism.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index f8520f2..6936195 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,7 +2,7 @@ use crate::*; /// Checks to see if the most recent host function call returned an error. pub fn get_host_func_error() -> Result<(), Error> { - let offset = unsafe { extism::host_func_get_error() }; + let offset = unsafe { extism::error_get() }; if offset == 0 { return Ok(()); } diff --git a/src/extism.rs b/src/extism.rs index 973fd3b..c0ab039 100644 --- a/src/extism.rs +++ b/src/extism.rs @@ -9,6 +9,7 @@ extern "C" { pub fn free(offs: u64); pub fn output_set(offs: u64, length: u64); pub fn error_set(offs: u64); + pub fn error_get() -> u64; pub fn store_u8(offs: u64, data: u8); pub fn load_u8(offs: u64) -> u8; pub fn store_u64(offs: u64, data: u64); From 9a29492548192c576da5daa3a4c338fe217b517d Mon Sep 17 00:00:00 2001 From: Clark McCauley Date: Thu, 5 Dec 2024 15:50:01 -0700 Subject: [PATCH 3/5] Stripe sentinel host function error prefix if provided --- src/error.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/src/error.rs b/src/error.rs index 6936195..9951fea 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,16 +1,59 @@ use crate::*; +use base64::prelude::*; +use std::sync::LazyLock; + + +/// If the host is appending a sentinel prefix to the host function errors +/// it will also pass that prefix to the guest, so the guest knows what to +/// trim off the front of the raw bytes. This is stored in a lazy lock because +/// it is sent once and never changed. Subsequent calls to get host function +/// errors will not need to recompute this value. +static HOST_FUNC_ERROR_PREFIX: LazyLock>> = LazyLock::new(|| { + std::env::var("EXTISM_HOST_FUNC_ERROR_PREFIX") + .ok() + .and_then(|p| BASE64_STANDARD.decode(p.as_bytes()).ok()) +}); /// Checks to see if the most recent host function call returned an error. +/// Retrieves any error set by the host function and stripes any sentinel bytes from it. +/// +/// This function interacts with the Extism kernel's `error_get` mechanism to fetch any +/// error currently stored in WASM memory. If an error exists, it determines whether the +/// error was created by a host function (using sentinel bytes in the prefix). +/// +/// ### Behavior: +/// - Calls `extism::error_get` to retrieve the current error's memory offset. +/// - If no error is present (offset is `0`), returns `Ok(())`. +/// - Attempts to locate the memory at the given offset and process the error bytes: +/// - If a known sentinel prefix is found, the function strips the prefix and treats +/// the remaining bytes as the error message from the host function. +/// - If no prefix is present, the error is returned as-is. +/// - Converts the error bytes into a UTF-8 string and wraps it in an `Error::msg`. +/// +/// ### Notes: +/// - Errors originating from host functions are marked with a unique sentinel prefix +/// (defined by `HOST_FUNC_ERROR_PREFIX`). This prefix is appended during serialization +/// by the host and removed during deserialization here, enabling reliable error +/// identification and processing. pub fn get_host_func_error() -> Result<(), Error> { let offset = unsafe { extism::error_get() }; if offset == 0 { return Ok(()); } - match Memory::find(offset) - .map(|mem| mem.to_string().ok()) - .flatten() - { - None => Ok(()), - Some(mem) => Err(Error::msg(mem.to_string())), + + if let Some(mem) = Memory::find(offset).map(|mem| mem.to_vec()) { + let message = if let Some(prefix) = HOST_FUNC_ERROR_PREFIX.as_ref() { + // Attempt to strip the sentinel prefix for host function errors + mem.strip_prefix(prefix.as_slice()) + .map(|stripped| String::from_utf8_lossy(stripped)) + .unwrap_or_else(|| String::from_utf8_lossy(&mem)) + } else { + // Handle the memory as a UTF-8 string if no prefix is available + String::from_utf8_lossy(&mem) + }; + + return Err(Error::msg(message.to_string())); } -} + + Ok(()) +} \ No newline at end of file From 2cc7992f2995accd971a525ac8cb279935d5732c Mon Sep 17 00:00:00 2001 From: Clark McCauley Date: Thu, 5 Dec 2024 16:01:48 -0700 Subject: [PATCH 4/5] Removed unused kernel func --- src/extism.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/extism.rs b/src/extism.rs index c0ab039..7a835e0 100644 --- a/src/extism.rs +++ b/src/extism.rs @@ -26,7 +26,6 @@ extern "C" { pub fn log_error(offs: u64); pub fn log_trace(offs: u64); pub fn get_log_level() -> i32; - pub fn host_func_get_error() -> u64; } /// Loads a byte array from Extism's memory. Only use this if you From 62dee863e163d20a4232f645fa35dcb62cc87333 Mon Sep 17 00:00:00 2001 From: Clark McCauley Date: Fri, 6 Dec 2024 09:59:56 -0700 Subject: [PATCH 5/5] Take error after reading --- src/error.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/error.rs b/src/error.rs index 9951fea..04b84af 100644 --- a/src/error.rs +++ b/src/error.rs @@ -41,6 +41,9 @@ pub fn get_host_func_error() -> Result<(), Error> { return Ok(()); } + // We need to reset the error now that we've read it + unsafe { extism::error_set(0) }; + if let Some(mem) = Memory::find(offset).map(|mem| mem.to_vec()) { let message = if let Some(prefix) = HOST_FUNC_ERROR_PREFIX.as_ref() { // Attempt to strip the sentinel prefix for host function errors