Compare commits

...

11 Commits

Author SHA1 Message Date
Benjamin Eckel
ec70b0e7b8 Release: 0.5.3 stable release (#538)
Cuts a new 0.5.3 release from stable.
2023-10-24 12:29:39 -05:00
zach
1452b9fb42 chore: backport latest kernel changes to stable (#528) 2023-10-20 10:16:33 -07:00
Benjamin Eckel
9f69a68df4 chore: fix elixir release action (#497)
This is 404ing for some reason. But Elixir doesn't need the shared
library so i'm removing it.

https://github.com/extism/extism/actions/runs/6425026823/job/17446865267
2023-10-05 17:32:20 -05:00
Benjamin Eckel
dbf9f34dc6 chore: Release elixir v0.5.1 (#496) 2023-10-05 17:14:54 -05:00
Benjamin Eckel
c8a9478da1 chore: also bump rust SDK to 0.5.2 2023-09-21 10:22:39 -05:00
Benjamin Eckel
e89ddd5a2a chore: Bump to 0.5.2 2023-09-21 09:41:27 -05:00
zach
93392e0884 fix(stable): improve the way the kernel calculates how many pages to allocate (#471)
Fixes a bug in the kernel memory allocator where the space used by the
`MemoryRoot` wasn't being considered

---------

Co-authored-by: Benjamin Eckel <bhelx@users.noreply.github.com>
2023-09-20 16:02:42 -07:00
Benjamin Eckel
4ebd0eb372 chore: always run the publish of SDK 2023-09-18 19:17:47 -05:00
zach
8feee0c693 cleanup(stable): use wasm-strip on extism-runtime.wasm (#467) 2023-09-18 19:08:27 -05:00
Benjamin Eckel
773ab32a45 chore: Bump to 0.5.1 2023-09-18 18:55:46 -05:00
Benjamin Eckel
6a041d0c39 fix: Fixes rounding issue in kernel num_pages (#466)
There were some scenarios where the kernel was not allocating enough
pages to get data back into the plugin. So some host functions were
failing when the output was right on this boundary.

Related https://github.com/moonrepo/proto/issues/208

We will come back with tests and cherry pick this over to main. For now
we want to get out a 0.5.1 fix
2023-09-18 18:53:30 -05:00
13 changed files with 140 additions and 44 deletions

View File

@@ -10,13 +10,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install extism shared library
shell: bash
run: |
mkdir -p /home/runner/.local/bin/
export PATH="/home/runner/.local/bin/:$PATH"
curl https://raw.githubusercontent.com/extism/cli/main/install.sh | sh
extism --sudo --prefix /usr/local install
- name: Setup Elixir Host SDK
uses: erlef/setup-beam@v1
with:

View File

@@ -29,6 +29,7 @@ jobs:
sleep 5
- name: Release Rust Host SDK
if: always()
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |

View File

@@ -4,7 +4,7 @@ defmodule Extism.MixProject do
def project do
[
app: :extism,
version: "0.5.0",
version: "0.5.1",
elixir: "~> 1.12",
start_permanent: Mix.env() == :prod,
deps: deps(),

View File

@@ -13,6 +13,6 @@ crate-type = ["cdylib"]
[workspace]
[dependencies]
rustler = "0.28.0"
extism = "0.5.0"
rustler = "0.29.1"
extism = "0.5.2"
log = "0.4"

View File

@@ -87,19 +87,22 @@ pub enum MemoryStatus {
///
/// The overall layout of the Extism-manged memory is organized like this:
/// |------|-------|---------|-------|--------------|
/// | Root | Block | Data | Block | Data | ...
/// |------|-------|---------|-------|--------------|
/// |------|-------+---------|-------+--------------|
/// | Root | Block + Data | Block + Data | ...
/// |------|-------+---------|-------+--------------|
///
/// Where `Root` and `Block` are fixed to the size of the `MemoryRoot` and `MemoryBlock` structs. But
/// the size of `Data` is dependent on the allocation size.
///
/// This means that the offset of a `Block` is the size of `Root` plus the size of all existing `Blocks`
/// including their data.
#[repr(C)]
pub struct MemoryRoot {
/// Position of the bump allocator, relative to `START_PAGE`
/// Position of the bump allocator, relative to `blocks` field
pub position: AtomicU64,
/// The total size of all data allocated using this allocator
pub length: AtomicU64,
/// A pointer to where the blocks begin
/// A pointer to the start of the first block
pub blocks: [MemoryBlock; 0],
}
@@ -119,9 +122,13 @@ pub struct MemoryBlock {
/// Returns the number of pages needed for the given number of bytes
pub fn num_pages(nbytes: u64) -> usize {
let nbytes = nbytes as f64;
let page = PAGE_SIZE as f64;
((nbytes / page) + 0.5) as usize
let npages = nbytes / PAGE_SIZE as u64;
let remainder = nbytes % PAGE_SIZE as u64;
if remainder != 0 {
(npages + 1) as usize
} else {
npages as usize
}
}
// Get the `MemoryRoot` at the correct offset in memory
@@ -176,6 +183,22 @@ impl MemoryRoot {
self.position.store(0, Ordering::Release);
}
#[inline(always)]
#[allow(unused)]
fn pointer_in_bounds(&self, p: Pointer) -> bool {
let start_ptr = self.blocks.as_ptr() as Pointer;
p >= start_ptr && p < start_ptr + self.length.load(Ordering::Acquire) as Pointer
}
#[inline(always)]
#[allow(unused)]
fn pointer_in_bounds_fast(p: Pointer) -> bool {
// Similar to `pointer_in_bounds` but less accurate on the upper bound. This uses the total memory size,
// instead of checking `MemoryRoot::length`
let end = core::arch::wasm32::memory_size(0) << 16;
p >= core::mem::size_of::<Self>() as Pointer && p <= end as Pointer
}
// Find a block that is free to use, this can be a new block or an existing freed block. The `self_position` argument
// is used to avoid loading the allocators position more than once when performing an allocation.
unsafe fn find_free_block(
@@ -190,6 +213,7 @@ impl MemoryRoot {
while (block as u64) < self.blocks.as_ptr() as u64 + self_position {
let b = &mut *block;
// Get the block status, this lets us know if we are able to re-use it
let status = b.status.load(Ordering::Acquire);
// An unused block is safe to use
@@ -242,13 +266,13 @@ impl MemoryRoot {
let curr = self.blocks.as_ptr() as u64 + self_position;
// Get the number of bytes available
let mem_left = self_length - self_position;
let mem_left = self_length - self_position - core::mem::size_of::<MemoryRoot>() as u64;
// When the allocation is larger than the number of bytes available
// we will need to try to grow the memory
if length >= mem_left {
// Calculate the number of pages needed to cover the remaining bytes
let npages = num_pages(length);
let npages = num_pages(length - mem_left);
let x = core::arch::wasm32::memory_grow(0, npages);
if x == usize::MAX {
return None;
@@ -276,8 +300,7 @@ impl MemoryRoot {
/// Finds the block at an offset in memory
pub unsafe fn find_block(&mut self, offs: Pointer) -> Option<&mut MemoryBlock> {
if offs >= self.blocks.as_ptr() as Pointer + self.length.load(Ordering::Acquire) as Pointer
{
if !Self::pointer_in_bounds_fast(offs) {
return None;
}
let ptr = offs - core::mem::size_of::<MemoryBlock>() as u64;
@@ -293,9 +316,7 @@ impl MemoryBlock {
/// is calculated based on metadata provided by the current block
#[inline]
pub unsafe fn next_ptr(&mut self) -> *mut MemoryBlock {
self.data
.as_mut_ptr()
.add(self.size + core::mem::size_of::<MemoryBlock>()) as *mut MemoryBlock
self.data.as_mut_ptr().add(self.size) as *mut MemoryBlock
}
/// Mark a block as free
@@ -305,11 +326,14 @@ impl MemoryBlock {
}
}
// Extism functions - these functions should be
// Extism functions
/// Allocate a block of memory and return the offset
#[no_mangle]
pub unsafe fn extism_alloc(n: Length) -> Pointer {
if n == 0 {
return 0;
}
let region = MemoryRoot::new();
let block = region.alloc(n);
match block {
@@ -321,9 +345,18 @@ pub unsafe fn extism_alloc(n: Length) -> Pointer {
/// Free allocated memory
#[no_mangle]
pub unsafe fn extism_free(p: Pointer) {
if p == 0 {
return;
}
let block = MemoryRoot::new().find_block(p);
if let Some(block) = block {
block.free();
// If the input pointer is freed for some reason, make sure the input length to 0
// since the original data is gone
if p == INPUT_OFFSET {
INPUT_LENGTH = 0;
}
}
}
@@ -343,57 +376,89 @@ pub unsafe fn extism_length(p: Pointer) -> Length {
/// Load a byte from Extism-managed memory
#[no_mangle]
pub unsafe fn extism_load_u8(p: Pointer) -> u8 {
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::pointer_in_bounds_fast(p) {
return 0;
}
*(p as *mut u8)
}
/// Load a u64 from Extism-managed memory
#[no_mangle]
pub unsafe fn extism_load_u64(p: Pointer) -> u64 {
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::pointer_in_bounds_fast(p + core::mem::size_of::<u64>() as u64 - 1) {
return 0;
}
*(p as *mut u64)
}
/// Load a byte from the input data
#[no_mangle]
pub unsafe fn extism_input_load_u8(p: Pointer) -> u8 {
#[cfg(feature = "bounds-checking")]
if p >= INPUT_LENGTH {
return 0;
}
*((INPUT_OFFSET + p) as *mut u8)
}
/// Load a u64 from the input data
#[no_mangle]
pub unsafe fn extism_input_load_u64(p: Pointer) -> u64 {
#[cfg(feature = "bounds-checking")]
if p + core::mem::size_of::<u64>() as Pointer > INPUT_LENGTH {
return 0;
}
*((INPUT_OFFSET + p) as *mut u64)
}
/// Write a byte in Extism-managed memory
#[no_mangle]
pub unsafe fn extism_store_u8(p: Pointer, x: u8) {
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::pointer_in_bounds_fast(p) {
return;
}
*(p as *mut u8) = x;
}
/// Write a u64 in Extism-managed memory
#[no_mangle]
pub unsafe fn extism_store_u64(p: Pointer, x: u64) {
unsafe {
*(p as *mut u64) = x;
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::pointer_in_bounds_fast(p + core::mem::size_of::<u64>() as u64 - 1) {
return;
}
*(p as *mut u64) = x;
}
/// Set the range of the input data in memory
#[no_mangle]
pub fn extism_input_set(p: Pointer, len: Length) {
unsafe {
INPUT_OFFSET = p;
INPUT_LENGTH = len;
pub unsafe fn extism_input_set(p: Pointer, len: Length) {
#[cfg(feature = "bounds-checking")]
{
let root = MemoryRoot::new();
if !root.pointer_in_bounds(p) || !root.pointer_in_bounds(p + len - 1) {
return;
}
}
INPUT_OFFSET = p;
INPUT_LENGTH = len;
}
/// Set the range of the output data in memory
#[no_mangle]
pub fn extism_output_set(p: Pointer, len: Length) {
unsafe {
OUTPUT_OFFSET = p;
OUTPUT_LENGTH = len;
pub unsafe fn extism_output_set(p: Pointer, len: Length) {
#[cfg(feature = "bounds-checking")]
{
let root = MemoryRoot::new();
if !root.pointer_in_bounds(p) || !root.pointer_in_bounds(p + len - 1) {
return;
}
}
OUTPUT_OFFSET = p;
OUTPUT_LENGTH = len;
}
/// Get the input length
@@ -430,6 +495,16 @@ pub unsafe fn extism_reset() {
/// Set the error message offset
#[no_mangle]
pub unsafe fn extism_error_set(ptr: Pointer) {
// Allow ERROR to be set to 0
if ptr == 0 {
ERROR.store(ptr, Ordering::SeqCst);
return;
}
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::new().pointer_in_bounds(ptr) {
return;
}
ERROR.store(ptr, Ordering::SeqCst);
}

View File

@@ -1,6 +1,6 @@
[package]
name = "libextism"
version = "0.5.0"
version = "0.5.3"
edition = "2021"
authors = ["The Extism Authors", "oss@extism.org"]
license = "BSD-3-Clause"

View File

@@ -1,6 +1,6 @@
[package]
name = "extism-runtime"
version = "0.5.0"
version = "0.5.3"
edition = "2021"
authors = ["The Extism Authors", "oss@extism.org"]
license = "BSD-3-Clause"

Binary file not shown.

View File

@@ -104,6 +104,9 @@ pub trait InternalExt {
}
fn memory_alloc(&mut self, n: Size) -> Result<u64, Error> {
if n == 0 {
return Ok(0);
}
let (linker, mut store) = self.linker_and_store();
let output = &mut [Val::I64(0)];
linker
@@ -113,7 +116,7 @@ pub trait InternalExt {
.unwrap()
.call(&mut store, &[Val::I64(n as i64)], output)?;
let offs = output[0].unwrap_i64() as u64;
if offs == 0 {
if offs == 0 && n > 0 {
anyhow::bail!("out of memory")
}
trace!("memory_alloc: {}, {}", offs, n);

View File

@@ -200,6 +200,7 @@ pub(crate) fn http_request(
Some(res.into_reader())
}
Err(e) => {
log::error!("Unable to make HTTP request: {:?}", e);
if let Some(res) = e.into_response() {
data.http_status = res.status();
Some(res.into_reader())

View File

@@ -1,6 +1,6 @@
[package]
name = "extism"
version = "0.5.0"
version = "0.5.3"
edition = "2021"
authors = ["The Extism Authors", "oss@extism.org"]
license = "BSD-3-Clause"
@@ -10,7 +10,7 @@ description = "Extism Host SDK for Rust"
[dependencies]
extism-manifest = { version = "0.5.0", path = "../manifest" }
extism-runtime = { version = "0.5.0", path = "../runtime"}
extism-runtime = { version = "0.5.3", path = "../runtime"}
serde_json = "1"
log = "0.4"
anyhow = "1"

View File

@@ -45,6 +45,7 @@ mod tests {
const WASM: &[u8] = include_bytes!("../../wasm/code-functions.wasm");
const WASM_LOOP: &[u8] = include_bytes!("../../wasm/loop.wasm");
const WASM_GLOBALS: &[u8] = include_bytes!("../../wasm/globals.wasm");
const REFLECT_WASM: &[u8] = include_bytes!("../../wasm/reflect.wasm");
fn hello_world(
plugin: &mut CurrentPlugin,
@@ -53,8 +54,8 @@ mod tests {
_user_data: UserData,
) -> Result<(), Error> {
let input_offs = inputs[0].unwrap_i64() as u64;
let input = plugin.memory_read_str(input_offs).unwrap().to_string();
let length = plugin.memory_length(input_offs);
let input = plugin.memory_read(input_offs, length).to_vec();
let output = plugin.memory_alloc_bytes(&input).unwrap();
outputs[0] = Val::I64(output as i64);
Ok(())
@@ -282,7 +283,7 @@ mod tests {
});
let start = std::time::Instant::now();
let _output = plugin.call("infinite_loop", "abc123");
let _output = plugin.call("infinite_loop", "");
let end = std::time::Instant::now();
let time = end - start;
println!("Cancelled plugin ran for {:?}", time);
@@ -319,4 +320,26 @@ mod tests {
assert_eq!(count.get("count").unwrap().as_i64().unwrap(), i);
}
}
#[test]
fn test_fuzz_reflect_plugin() {
// assert!(set_log_file("stdout", Some(log::Level::Trace)));
let f = Function::new(
"host_reflect",
[ValType::I64],
[ValType::I64],
None,
hello_world,
);
let context = Context::new();
let mut plugin = Plugin::new(&context, REFLECT_WASM, [f], true).unwrap();
for i in 1..65540 {
let input = "a".repeat(i);
let output = plugin.call("reflect", input.clone());
let output = std::str::from_utf8(output.unwrap()).unwrap();
assert_eq!(output, input);
}
}
}

BIN
wasm/reflect.wasm Normal file

Binary file not shown.