Skip to content

Commit 67bc6b7

Browse files
authored
feat: CompiledPlugin with fuel limit (#143)
This follows extism/extism#883 and Extism 1.13.0, which allows to create a CompiledPlugin with a fuel limit. Previously, we could only create a (non-compiled) Plugin with a fuel limit, or a CompiledPlugin without a fuel limit. A CompiledPlugin with a fuel limit is interesting for the scenario where: - the host is instantiating many times the same wasm file (for example, a Python interpreter) - and needs a fuel limit to ensure resource usage is controlled while running untrusted scripts On practical example is when using Extism to implement a Python sandbox in a .NET host, that is exposed as a tool to an LLM chatbot embedded in the app. The LLM can generate Python code that calls host functions or perform calculations to fulfill the user's request. Setting a fuel limit is important for the security of the approach, since the executed code cannot be trusted, as it is produced by the LLM. The fuel limit ensures that resources stay in check, along with memory limits, time-based cancellation, and the isolation properties of WASM. Until now, we could only use the non-compiled Plugin, since this one exposes the fuel limit. But it makes sense to use the CompiledPlugin, since the same wasm (the Python interpreter) is used over and over again.
1 parent 629608a commit 67bc6b7

8 files changed

Lines changed: 62 additions & 13 deletions

File tree

samples/Extism.Sdk.FSharpSample/Extism.Sdk.FSharpSample.fsproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</ItemGroup>
1212

1313
<ItemGroup>
14-
<PackageReference Include="Extism.runtime.win-x64" Version="1.9.1" />
14+
<PackageReference Include="Extism.runtime.win-x64" Version="1.13.0" />
1515
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
1616
</ItemGroup>
1717

samples/Extism.Sdk.Sample/Extism.Sdk.Sample.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
</ItemGroup>
1818

1919
<ItemGroup>
20-
<PackageReference Include="Extism.runtime.all" Version="1.9.1" />
20+
<PackageReference Include="Extism.runtime.all" Version="1.13.0" />
2121
</ItemGroup>
2222

2323
<ItemGroup>

src/Extism.Sdk/LibExtism.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ internal struct ExtismCurrentPlugin { }
237237
/// <param name="plugin">Pointer to the plugin you want to free.</param>
238238
[DllImport("extism")]
239239
unsafe internal static extern void extism_plugin_free(ExtismPlugin* plugin);
240+
240241
/// <summary>
241242
/// Pre-compile an Extism plugin
242243
/// </summary>
@@ -250,6 +251,20 @@ internal struct ExtismCurrentPlugin { }
250251
[DllImport("extism")]
251252
unsafe internal static extern ExtismCompiledPlugin* extism_compiled_plugin_new(byte* wasm, long wasmSize, IntPtr* functions, long nFunctions, [MarshalAs(UnmanagedType.I1)] bool withWasi, out char** errmsg);
252253

254+
/// <summary>
255+
/// Pre-compile an Extism plugin with fuel limit
256+
/// </summary>
257+
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
258+
/// <param name="wasmSize">The length of the `wasm` parameter.</param>
259+
/// <param name="functions">Array of host function pointers.</param>
260+
/// <param name="nFunctions">Number of host functions.</param>
261+
/// <param name="withWasi">Enables/disables WASI.</param>
262+
/// <param name="fuelLimit">Max number of instructions that can be executed by the plugin.</param>
263+
/// <param name="errmsg"></param>
264+
/// <returns></returns>
265+
[DllImport("extism")]
266+
unsafe internal static extern ExtismCompiledPlugin* extism_compiled_plugin_new_with_fuel_limit(byte* wasm, long wasmSize, IntPtr* functions, long nFunctions, [MarshalAs(UnmanagedType.I1)] bool withWasi, long fuelLimit, out char** errmsg);
267+
253268
/// <summary>
254269
/// Free `ExtismCompiledPlugin`
255270
/// </summary>

src/Extism.Sdk/Plugin.cs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -541,20 +541,30 @@ public unsafe class CompiledPlugin : IDisposable
541541
/// <param name="manifest"></param>
542542
/// <param name="functions"></param>
543543
/// <param name="withWasi"></param>
544-
public CompiledPlugin(Manifest manifest, HostFunction[] functions, bool withWasi)
544+
public CompiledPlugin(Manifest manifest, HostFunction[] functions, bool withWasi) : this(manifest, functions, new PluginIntializationOptions { WithWasi = withWasi })
545+
{
546+
}
547+
548+
/// <summary>
549+
/// Compile a plugin from a Manifest.
550+
/// </summary>
551+
/// <param name="manifest"></param>
552+
/// <param name="functions"></param>
553+
/// <param name="options"></param>
554+
public CompiledPlugin(Manifest manifest, HostFunction[] functions, PluginIntializationOptions options)
545555
{
546556
Functions = functions;
547557

548-
var options = new JsonSerializerOptions
558+
var jsonOptions = new JsonSerializerOptions
549559
{
550560
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
551561
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
552562
};
553563

554-
options.Converters.Add(new WasmSourceConverter());
555-
options.Converters.Add(new JsonStringEnumConverter<HttpMethod>());
564+
jsonOptions.Converters.Add(new WasmSourceConverter());
565+
jsonOptions.Converters.Add(new JsonStringEnumConverter<HttpMethod>());
556566

557-
var jsonContext = new ManifestJsonContext(options);
567+
var jsonContext = new ManifestJsonContext(jsonOptions);
558568
var json = JsonSerializer.Serialize(manifest, jsonContext.Manifest);
559569

560570
var bytes = Encoding.UTF8.GetBytes(json);
@@ -563,7 +573,7 @@ public CompiledPlugin(Manifest manifest, HostFunction[] functions, bool withWasi
563573
fixed (byte* wasmPtr = bytes)
564574
fixed (IntPtr* functionsPtr = functionHandles)
565575
{
566-
NativeHandle = Initialize(wasmPtr, bytes.Length, functions, withWasi, functionsPtr);
576+
NativeHandle = Initialize(wasmPtr, bytes.Length, functions, options.WithWasi, options.FuelLimit, functionsPtr);
567577
}
568578
}
569579

@@ -577,11 +587,14 @@ public Plugin Instantiate()
577587
return new Plugin(this);
578588
}
579589

580-
private unsafe LibExtism.ExtismCompiledPlugin* Initialize(byte* wasmPtr, int wasmLength, HostFunction[] functions, bool withWasi, IntPtr* functionsPtr)
590+
private unsafe LibExtism.ExtismCompiledPlugin* Initialize(byte* wasmPtr, int wasmLength, HostFunction[] functions, bool withWasi, long? fuelLimit, IntPtr* functionsPtr)
581591
{
582592
char** errorMsgPtr;
583593

584-
var handle = LibExtism.extism_compiled_plugin_new(wasmPtr, wasmLength, functionsPtr, functions.Length, withWasi, out errorMsgPtr);
594+
LibExtism.ExtismCompiledPlugin* handle = fuelLimit.HasValue
595+
? LibExtism.extism_compiled_plugin_new_with_fuel_limit(wasmPtr, wasmLength, functionsPtr, functions.Length, withWasi, fuelLimit.Value, out errorMsgPtr)
596+
: LibExtism.extism_compiled_plugin_new(wasmPtr, wasmLength, functionsPtr, functions.Length, withWasi, out errorMsgPtr);
597+
585598
if (handle == null)
586599
{
587600
var msg = "Unable to compile plugin";

test/Extism.Sdk.Benchmarks/Extism.Sdk.Benchmarks.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
</ItemGroup>
1919

2020
<ItemGroup>
21-
<PackageReference Include="Extism.runtime.win-x64" Version="1.9.1" />
21+
<PackageReference Include="Extism.runtime.win-x64" Version="1.13.0" />
2222
</ItemGroup>
2323

2424
<ItemGroup>

test/Extism.Sdk/CompiledPluginTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,19 @@ long HelloWorld(CurrentPlugin plugin, long ptr)
5656
}
5757
}
5858

59+
[Fact]
60+
public void FuelLimit()
61+
{
62+
using var compiledPlugin = Helpers.CompilePlugin("loop.wasm", options: new PluginIntializationOptions
63+
{
64+
FuelLimit = 1000,
65+
WithWasi = true
66+
});
67+
68+
using var plugin = compiledPlugin.Instantiate();
69+
70+
Should.Throw<ExtismException>(() => plugin.Call("loop_forever", Array.Empty<byte>()))
71+
.Message.ShouldContain("fuel");
72+
}
73+
5974
}

test/Extism.Sdk/Extism.Sdk.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="Extism.runtime.all" Version="1.9.1" />
12+
<PackageReference Include="Extism.runtime.all" Version="1.13.0" />
1313
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
1414
<PackageReference Include="Shouldly" Version="4.3.0" />
1515
<PackageReference Include="xunit" Version="2.9.3" />

test/Extism.Sdk/Helpers.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ public static Plugin LoadPlugin(string name, Action<Manifest>? config = null, pa
2626
}
2727

2828
public static CompiledPlugin CompilePlugin(string name, Action<Manifest>? config = null, params HostFunction[] hostFunctions)
29+
{
30+
var options = new PluginIntializationOptions { WithWasi = true };
31+
return CompilePlugin(name, options, config, hostFunctions);
32+
}
33+
34+
public static CompiledPlugin CompilePlugin(string name, PluginIntializationOptions options, Action<Manifest>? config = null, params HostFunction[] hostFunctions)
2935
{
3036
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
3137
var manifest = new Manifest(new PathWasmSource(Path.Combine(binDirectory, "wasm", name), "main"));
@@ -34,6 +40,6 @@ public static CompiledPlugin CompilePlugin(string name, Action<Manifest>? config
3440
config(manifest);
3541
}
3642

37-
return new CompiledPlugin(manifest, hostFunctions, withWasi: true);
43+
return new CompiledPlugin(manifest, hostFunctions, options);
3844
}
3945
}

0 commit comments

Comments
 (0)