mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat(ffi): unwind safety: shield FFI entrypoints with panic guard (#546)
This PR implements widely accepted Rust programming practices for dealing with panics across ABI (programming language) boundaries. - Add panic_guard.rs to wrap FFI calls and prevent panic across FFI/ABI boundary (undefined behavior). - Capture per-thread backtraces via a temporary panic hook - After a panic, subsequent invocations are poisoned. - Integrate with_unwind_guard across the engine, schema registry, and target registry exportis Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
9426b2ec02
commit
80686d6ed1
4
.github/workflows/test-csharp.yml
vendored
4
.github/workflows/test-csharp.yml
vendored
@@ -144,11 +144,11 @@ jobs:
|
||||
path: ./bindings/csharp/regorus-nuget/
|
||||
|
||||
- name: Restore Regorus.Tests
|
||||
run: dotnet restore /p:RestoreAdditionalProjectSources=../regorus-nuget
|
||||
run: dotnet restore /p:RestoreAdditionalProjectSources=../regorus-nuget /p:UseLocalRegorus=false
|
||||
working-directory: ./bindings/csharp/Regorus.Tests
|
||||
|
||||
- name: Run Regorus.Tests
|
||||
run: dotnet test --no-restore
|
||||
run: dotnet test --no-restore -p:UseLocalRegorus=false
|
||||
working-directory: ./bindings/csharp/Regorus.Tests
|
||||
|
||||
- name: Restore TestApp
|
||||
|
||||
84
bindings/csharp/Regorus.Tests/PanicGuardTests.cs
Normal file
84
bindings/csharp/Regorus.Tests/PanicGuardTests.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
#if REGORUS_FFI_TEST_HOOKS
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Regorus.Internal;
|
||||
|
||||
namespace Regorus.Tests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class PanicGuardTests
|
||||
{
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
API.regorus_engine_test_reset_poison();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
API.regorus_engine_test_reset_poison();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Panic_produces_invalid_operation_exception()
|
||||
{
|
||||
var panic = Assert.ThrowsException<InvalidOperationException>(TriggerPanic);
|
||||
StringAssert.Contains(panic.Message, "panicked", "panic message should capture payload");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Poison_flag_blocks_subsequent_calls()
|
||||
{
|
||||
_ = Assert.ThrowsException<InvalidOperationException>(TriggerPanic);
|
||||
var poisoned = Assert.ThrowsException<InvalidOperationException>(TriggerPanic);
|
||||
StringAssert.Contains(poisoned.Message, "poisoned", "poisoned message should explain guard state");
|
||||
}
|
||||
|
||||
private static unsafe void TriggerPanic()
|
||||
{
|
||||
var result = API.regorus_engine_test_trigger_panic();
|
||||
try
|
||||
{
|
||||
if (result.status == RegorusStatus.Ok)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = PtrToStringUtf8((IntPtr)result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? PtrToStringUtf8(IntPtr ptr)
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
return Marshal.PtrToStringUTF8(ptr);
|
||||
#else
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var len = 0;
|
||||
while (Marshal.ReadByte(ptr, len) != 0)
|
||||
{
|
||||
len++;
|
||||
}
|
||||
|
||||
var buffer = new byte[len];
|
||||
Marshal.Copy(ptr, buffer, 0, buffer.Length);
|
||||
return System.Text.Encoding.UTF8.GetString(buffer);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -6,11 +6,17 @@
|
||||
<!-- More info about dotnet test integration https://learn.microsoft.com/dotnet/core/testing/unit-testing-platform-integration-dotnet-test -->
|
||||
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
|
||||
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- If the environment variable is set (such as in a Github Action run), append the suffix to the version number -->
|
||||
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
|
||||
<UseLocalRegorus Condition="'$(UseLocalRegorus)' == ''">true</UseLocalRegorus>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(UseLocalRegorus)' == 'true'">
|
||||
<DefineConstants>$(DefineConstants);REGORUS_FFI_TEST_HOOKS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -21,7 +27,13 @@
|
||||
<PackageReference Include="MSTest" Version="3.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Regorus" Version="0.8.0$(RegorusPackageVersionSuffix)"/>
|
||||
<ItemGroup Condition="'$(UseLocalRegorus)' == 'true'">
|
||||
<ProjectReference Include="../Regorus/Regorus.csproj">
|
||||
<AdditionalProperties>EnableRegorusTestHooks=true</AdditionalProperties>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(UseLocalRegorus)' != 'true'">
|
||||
<PackageReference Include="Regorus" Version="0.8.0$(RegorusPackageVersionSuffix)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
3
bindings/csharp/Regorus/AssemblyInfo.cs
Normal file
3
bindings/csharp/Regorus/AssemblyInfo.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Regorus.Tests")]
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
@@ -165,7 +166,7 @@ namespace Regorus
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace Regorus
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown compilation error occurred");
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
if (result.data_type != Internal.RegorusDataType.Pointer || result.pointer_value == null)
|
||||
|
||||
@@ -373,21 +373,21 @@ namespace Regorus
|
||||
|
||||
string? CheckAndDropResult(Regorus.Internal.RegorusResult result)
|
||||
{
|
||||
if (result.status != Regorus.Internal.RegorusStatus.Ok)
|
||||
try
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
var ex = new Exception(message);
|
||||
Regorus.Internal.API.regorus_result_drop(result);
|
||||
throw ex;
|
||||
}
|
||||
if (result.status != Regorus.Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
var resultString = "";
|
||||
if (result.output is not null)
|
||||
{
|
||||
resultString = StringFromUTF8((IntPtr)result.output);
|
||||
var output = result.output is not null ? StringFromUTF8((IntPtr)result.output) : null;
|
||||
return output ?? string.Empty;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Regorus.Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
Regorus.Internal.API.regorus_result_drop(result);
|
||||
return resultString;
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
|
||||
@@ -217,6 +217,20 @@ namespace Regorus.Internal
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_compile_with_entrypoint", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_engine_compile_with_entrypoint(RegorusEngine* engine, byte* rule);
|
||||
|
||||
#if REGORUS_FFI_TEST_HOOKS
|
||||
/// <summary>
|
||||
/// Trigger a panic inside the engine for testing purposes.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_test_trigger_panic", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_engine_test_trigger_panic();
|
||||
|
||||
/// <summary>
|
||||
/// Reset the engine poison flag for testing.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_test_reset_poison", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern void regorus_engine_test_reset_poison();
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Compilation Methods
|
||||
@@ -472,6 +486,14 @@ namespace Regorus.Internal
|
||||
/// Invalid policy content.
|
||||
/// </summary>
|
||||
InvalidPolicy,
|
||||
/// <summary>
|
||||
/// The engine panicked and cannot be reused until reset.
|
||||
/// </summary>
|
||||
Panic,
|
||||
/// <summary>
|
||||
/// The engine remains poisoned because a previous panic was detected.
|
||||
/// </summary>
|
||||
Poisoned,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(EnableRegorusTestHooks)' == 'true'">
|
||||
<DefineConstants>$(DefineConstants);REGORUS_FFI_TEST_HOOKS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
$(RegorusFFIArtifactsDir) is the location where regorus shared libraries have been
|
||||
built for various platforms and copied to. RegorusFFIArtifactsDir is passed in
|
||||
|
||||
@@ -240,7 +240,7 @@ namespace Regorus
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
@@ -265,7 +265,7 @@ namespace Regorus
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type == Internal.RegorusDataType.Boolean ? result.bool_value : false;
|
||||
@@ -283,7 +283,7 @@ namespace Regorus
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type == Internal.RegorusDataType.Integer ? result.int_value : 0;
|
||||
|
||||
24
bindings/csharp/Regorus/StatusExtensions.cs
Normal file
24
bindings/csharp/Regorus/StatusExtensions.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Regorus.Internal
|
||||
{
|
||||
internal static class StatusExtensions
|
||||
{
|
||||
internal static Exception CreateException(this RegorusStatus status, string? message)
|
||||
{
|
||||
var details = string.IsNullOrWhiteSpace(message) ? "Regorus call failed." : message;
|
||||
|
||||
return status switch
|
||||
{
|
||||
RegorusStatus.Panic => new InvalidOperationException($"Regorus engine panicked: {details}"),
|
||||
RegorusStatus.Poisoned => new InvalidOperationException($"Regorus engine is poisoned: {details}"),
|
||||
_ => new Exception(details),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,7 @@ namespace Regorus
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
@@ -160,7 +160,7 @@ namespace Regorus
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type == Internal.RegorusDataType.Boolean ? result.bool_value : false;
|
||||
@@ -178,7 +178,7 @@ namespace Regorus
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type == Internal.RegorusDataType.Integer ? result.int_value : 0;
|
||||
|
||||
@@ -34,6 +34,12 @@ pub enum RegorusStatus {
|
||||
|
||||
/// Invalid policy content.
|
||||
InvalidPolicy,
|
||||
|
||||
/// The engine panicked and cannot be reused until reset.
|
||||
Panic,
|
||||
|
||||
/// The engine remains poisoned because a previous panic was detected.
|
||||
Poisoned,
|
||||
}
|
||||
|
||||
/// Type of data contained in RegorusResult
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT License.
|
||||
use crate::common::{from_c_str, RegorusResult, RegorusStatus};
|
||||
use crate::compiled_policy::RegorusCompiledPolicy;
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::format;
|
||||
use alloc::vec::Vec;
|
||||
@@ -43,55 +44,54 @@ pub extern "C" fn regorus_compile_policy_with_entrypoint(
|
||||
modules_len: usize,
|
||||
entry_point_rule: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let data_str = match from_c_str(data_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid data JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let data_str = match from_c_str(data_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid data JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let entry_rule = match from_c_str(entry_point_rule) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidEntrypoint,
|
||||
format!("Invalid entry point rule string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let entry_rule = match from_c_str(entry_point_rule) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidEntrypoint,
|
||||
format!("Invalid entry point rule string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Parse data JSON
|
||||
let data = match Value::from_json_str(&data_str) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse data JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let data = match Value::from_json_str(&data_str) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse data JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Convert C modules array to Rust Vec
|
||||
let policy_modules = match convert_c_modules_to_rust(modules, modules_len) {
|
||||
Ok(modules) => modules,
|
||||
Err(status) => return RegorusResult::err(status),
|
||||
};
|
||||
let policy_modules = match convert_c_modules_to_rust(modules, modules_len) {
|
||||
Ok(modules) => modules,
|
||||
Err(status) => return RegorusResult::err(status),
|
||||
};
|
||||
|
||||
// Call the convenience function
|
||||
match compile_policy_with_entrypoint(data, &policy_modules, entry_rule.into()) {
|
||||
Ok(compiled_policy) => {
|
||||
let wrapped_policy = RegorusCompiledPolicy { compiled_policy };
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
match compile_policy_with_entrypoint(data, &policy_modules, entry_rule.into()) {
|
||||
Ok(compiled_policy) => {
|
||||
let wrapped_policy = RegorusCompiledPolicy { compiled_policy };
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Policy compilation failed: {e}"),
|
||||
),
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Policy compilation failed: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Compiles a target-aware policy from data and modules.
|
||||
@@ -122,45 +122,44 @@ pub extern "C" fn regorus_compile_policy_for_target(
|
||||
modules: *const RegorusPolicyModule,
|
||||
modules_len: usize,
|
||||
) -> RegorusResult {
|
||||
let data_str = match from_c_str(data_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid data JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let data_str = match from_c_str(data_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid data JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Parse data JSON
|
||||
let data = match Value::from_json_str(&data_str) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse data JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let data = match Value::from_json_str(&data_str) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse data JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Convert C modules array to Rust Vec
|
||||
let policy_modules = match convert_c_modules_to_rust(modules, modules_len) {
|
||||
Ok(modules) => modules,
|
||||
Err(status) => return RegorusResult::err(status),
|
||||
};
|
||||
let policy_modules = match convert_c_modules_to_rust(modules, modules_len) {
|
||||
Ok(modules) => modules,
|
||||
Err(status) => return RegorusResult::err(status),
|
||||
};
|
||||
|
||||
// Call the convenience function
|
||||
match compile_policy_for_target(data, &policy_modules) {
|
||||
Ok(compiled_policy) => {
|
||||
let wrapped_policy = RegorusCompiledPolicy { compiled_policy };
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
match compile_policy_for_target(data, &policy_modules) {
|
||||
Ok(compiled_policy) => {
|
||||
let wrapped_policy = RegorusCompiledPolicy { compiled_policy };
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Target-aware policy compilation failed: {e}"),
|
||||
),
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Target-aware policy compilation failed: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper function to convert C module array to Rust Vec<PolicyModule>.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::common::*;
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
use anyhow::Result;
|
||||
@@ -35,18 +36,20 @@ pub extern "C" fn regorus_compiled_policy_eval_with_input(
|
||||
compiled_policy: *mut RegorusCompiledPolicy,
|
||||
input: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let input_value = regorus::Value::from_json_str(&from_c_str(input)?)?;
|
||||
let result = to_ref(compiled_policy)?
|
||||
.compiled_policy
|
||||
.eval_with_input(input_value)?;
|
||||
result.to_json_str()
|
||||
}();
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let input_value = regorus::Value::from_json_str(&from_c_str(input)?)?;
|
||||
let result = to_ref(compiled_policy)?
|
||||
.compiled_policy
|
||||
.eval_with_input(input_value)?;
|
||||
result.to_json_str()
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get information about the compiled policy including metadata about modules,
|
||||
@@ -59,14 +62,16 @@ pub extern "C" fn regorus_compiled_policy_eval_with_input(
|
||||
pub extern "C" fn regorus_compiled_policy_get_policy_info(
|
||||
compiled_policy: *mut RegorusCompiledPolicy,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let info = to_ref(compiled_policy)?.compiled_policy.get_policy_info()?;
|
||||
serde_json::to_string(&info)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to serialize policy info: {}", e))
|
||||
}();
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let info = to_ref(compiled_policy)?.compiled_policy.get_policy_info()?;
|
||||
serde_json::to_string(&info)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to serialize policy info: {}", e))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#![cfg(feature = "azure_policy")]
|
||||
use crate::common::{from_c_str, RegorusResult, RegorusStatus};
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use regorus::{registry::schemas, Schema};
|
||||
|
||||
use std::os::raw::c_char;
|
||||
@@ -29,45 +30,45 @@ pub extern "C" fn regorus_effect_schema_register(
|
||||
name: *const c_char,
|
||||
schema_json: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid effect schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid effect schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let schema_str = match from_c_str(schema_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid effect schema JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let schema_str = match from_c_str(schema_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid effect schema JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Parse schema from JSON
|
||||
let schema = match Schema::from_json_str(&schema_str) {
|
||||
Ok(schema) => schema,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse effect schema JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let schema = match Schema::from_json_str(&schema_str) {
|
||||
Ok(schema) => schema,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse effect schema JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Register the schema
|
||||
match schemas::effect::register(schema_name, schema.into()) {
|
||||
Ok(()) => RegorusResult::ok_pointer(std::ptr::null_mut()),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to register effect schema: {e}"),
|
||||
),
|
||||
}
|
||||
match schemas::effect::register(schema_name, schema.into()) {
|
||||
Ok(()) => RegorusResult::ok_pointer(std::ptr::null_mut()),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to register effect schema: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if an effect schema with the given name exists.
|
||||
@@ -83,18 +84,20 @@ pub extern "C" fn regorus_effect_schema_register(
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_effect_schema_contains(name: *const c_char) -> RegorusResult {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid effect schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid effect schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let contains = schemas::effect::contains(&schema_name);
|
||||
RegorusResult::ok_bool(contains)
|
||||
let contains = schemas::effect::contains(&schema_name);
|
||||
RegorusResult::ok_bool(contains)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the number of registered effect schemas.
|
||||
@@ -104,8 +107,10 @@ pub extern "C" fn regorus_effect_schema_contains(name: *const c_char) -> Regorus
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_effect_schema_len() -> RegorusResult {
|
||||
let count = schemas::effect::len();
|
||||
RegorusResult::ok_int(count as i64)
|
||||
with_unwind_guard(|| {
|
||||
let count = schemas::effect::len();
|
||||
RegorusResult::ok_int(count as i64)
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if the effect schema registry is empty.
|
||||
@@ -115,8 +120,10 @@ pub extern "C" fn regorus_effect_schema_len() -> RegorusResult {
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_effect_schema_is_empty() -> RegorusResult {
|
||||
let is_empty = schemas::effect::is_empty();
|
||||
RegorusResult::ok_bool(is_empty)
|
||||
with_unwind_guard(|| {
|
||||
let is_empty = schemas::effect::is_empty();
|
||||
RegorusResult::ok_bool(is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
/// List all registered effect schema names as a JSON array.
|
||||
@@ -126,14 +133,16 @@ pub extern "C" fn regorus_effect_schema_is_empty() -> RegorusResult {
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_effect_schema_list_names() -> RegorusResult {
|
||||
let names = schemas::effect::list_names();
|
||||
match serde_json::to_string(&names) {
|
||||
Ok(json_str) => RegorusResult::ok_string(json_str),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to serialize effect schema names to JSON: {e}"),
|
||||
),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let names = schemas::effect::list_names();
|
||||
match serde_json::to_string(&names) {
|
||||
Ok(json_str) => RegorusResult::ok_string(json_str),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to serialize effect schema names to JSON: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove an effect schema by name.
|
||||
@@ -149,18 +158,20 @@ pub extern "C" fn regorus_effect_schema_list_names() -> RegorusResult {
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_effect_schema_remove(name: *const c_char) -> RegorusResult {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid effect schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid effect schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let removed = schemas::effect::remove(&schema_name).is_some();
|
||||
RegorusResult::ok_bool(removed)
|
||||
let removed = schemas::effect::remove(&schema_name).is_some();
|
||||
RegorusResult::ok_bool(removed)
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear all effect schemas from the registry.
|
||||
@@ -170,6 +181,8 @@ pub extern "C" fn regorus_effect_schema_remove(name: *const c_char) -> RegorusRe
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_effect_schema_clear() -> RegorusResult {
|
||||
schemas::effect::clear();
|
||||
RegorusResult::ok_pointer(std::ptr::null_mut())
|
||||
with_unwind_guard(|| {
|
||||
schemas::effect::clear();
|
||||
RegorusResult::ok_pointer(std::ptr::null_mut())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::common::{
|
||||
};
|
||||
use crate::compiled_policy::RegorusCompiledPolicy;
|
||||
use crate::lock::{new_handle, read, try_read, try_write, Handle, ReadGuard, WriteGuard};
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
@@ -67,6 +68,101 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod panic_tests {
|
||||
use super::{
|
||||
regorus_engine_drop, regorus_engine_eval_query, regorus_engine_get_policies,
|
||||
regorus_engine_new,
|
||||
};
|
||||
use crate::common::{regorus_result_drop, RegorusStatus};
|
||||
use crate::panic_guard::{is_poisoned, reset_poison};
|
||||
use alloc::boxed::Box;
|
||||
use regorus::Value;
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
#[test]
|
||||
fn catches_extension_panics_and_marks_poison() {
|
||||
reset_poison();
|
||||
|
||||
let engine_ptr = regorus_engine_new();
|
||||
assert!(!engine_ptr.is_null(), "engine allocation must succeed");
|
||||
assert!(!is_poisoned(), "guard must start unpoisoned");
|
||||
|
||||
unsafe {
|
||||
let engine = &mut *engine_ptr;
|
||||
{
|
||||
let mut guard = engine
|
||||
.try_write()
|
||||
.expect("exclusive access to configure engine");
|
||||
guard
|
||||
.add_extension(
|
||||
"panic_extension".to_string(),
|
||||
0,
|
||||
Box::new(|_| -> anyhow::Result<Value> { panic!("ffi extension panic") }),
|
||||
)
|
||||
.expect("extension registration must succeed");
|
||||
guard
|
||||
.add_policy(
|
||||
"panic.rego".to_string(),
|
||||
"package panic\n\ndefault allow = false\n\nallow if {\n panic_extension()\n}"
|
||||
.to_string(),
|
||||
)
|
||||
.expect("policy registration must succeed");
|
||||
}
|
||||
}
|
||||
|
||||
let query = CString::new("data.panic.allow").expect("valid query string");
|
||||
let panic_result = regorus_engine_eval_query(engine_ptr, query.as_ptr());
|
||||
assert!(matches!(panic_result.status, RegorusStatus::Panic));
|
||||
unsafe {
|
||||
assert!(
|
||||
!panic_result.error_message.is_null(),
|
||||
"panic details must be present"
|
||||
);
|
||||
let message = CStr::from_ptr(panic_result.error_message)
|
||||
.to_str()
|
||||
.expect("error message utf8");
|
||||
assert!(
|
||||
message.contains("ffi extension panic"),
|
||||
"panic payload must bubble across guard"
|
||||
);
|
||||
}
|
||||
regorus_result_drop(panic_result);
|
||||
assert!(is_poisoned(), "engine must be marked poisoned after panic");
|
||||
|
||||
let poisoned_result = regorus_engine_get_policies(engine_ptr);
|
||||
assert!(matches!(poisoned_result.status, RegorusStatus::Poisoned));
|
||||
unsafe {
|
||||
assert!(
|
||||
!poisoned_result.error_message.is_null(),
|
||||
"poison message must be present"
|
||||
);
|
||||
let message = CStr::from_ptr(poisoned_result.error_message)
|
||||
.to_str()
|
||||
.expect("poison message utf8");
|
||||
assert!(
|
||||
message.contains("regorus is poisoned"),
|
||||
"poison message must inform callers"
|
||||
);
|
||||
}
|
||||
regorus_result_drop(poisoned_result);
|
||||
|
||||
regorus_engine_drop(engine_ptr);
|
||||
reset_poison();
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "std")]
|
||||
pub extern "C" fn regorus_engine_test_trigger_panic() -> RegorusResult {
|
||||
with_unwind_guard(|| panic!("regorus ffi test panic"))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_test_reset_poison() {
|
||||
crate::panic_guard::reset_poison();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
/// Construct a new Engine
|
||||
///
|
||||
@@ -116,11 +212,13 @@ pub extern "C" fn regorus_engine_add_policy(
|
||||
path: *const c_char,
|
||||
rego: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_policy(from_c_str(path)?, from_c_str(rego)?)
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_policy(from_c_str(path)?, from_c_str(rego)?)
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
@@ -129,11 +227,13 @@ pub extern "C" fn regorus_engine_add_policy_from_file(
|
||||
engine: *mut RegorusEngine,
|
||||
path: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_policy_from_file(from_c_str(path)?)
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_policy_from_file(from_c_str(path)?)
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Add policy data.
|
||||
@@ -145,11 +245,13 @@ pub extern "C" fn regorus_engine_add_data_json(
|
||||
engine: *mut RegorusEngine,
|
||||
data: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_data(regorus::Value::from_json_str(&from_c_str(data)?)?)
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_data(regorus::Value::from_json_str(&from_c_str(data)?)?)
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Get list of loaded Rego packages as JSON.
|
||||
@@ -157,11 +259,13 @@ pub extern "C" fn regorus_engine_add_data_json(
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_packages()?).map_err(anyhow::Error::msg)
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_packages()?).map_err(anyhow::Error::msg)
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Get list of policies as JSON.
|
||||
@@ -169,11 +273,13 @@ pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> Reg
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_policies
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_get_policies(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_policies_as_json()
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_policies_as_json()
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
@@ -182,11 +288,13 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
|
||||
engine: *mut RegorusEngine,
|
||||
path: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_data(regorus::Value::from_json_file(from_c_str(path)?)?)
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_data(regorus::Value::from_json_file(from_c_str(path)?)?)
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear policy data.
|
||||
@@ -194,12 +302,14 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_data
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_data();
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_data();
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Set input.
|
||||
@@ -211,12 +321,14 @@ pub extern "C" fn regorus_engine_set_input_json(
|
||||
engine: *mut RegorusEngine,
|
||||
input: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_input(regorus::Value::from_json_str(&from_c_str(input)?)?);
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_input(regorus::Value::from_json_str(&from_c_str(input)?)?);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
@@ -225,12 +337,14 @@ pub extern "C" fn regorus_engine_set_input_from_json_file(
|
||||
engine: *mut RegorusEngine,
|
||||
path: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_input(regorus::Value::from_json_file(from_c_str(path)?)?);
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_input(regorus::Value::from_json_file(from_c_str(path)?)?);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Evaluate query.
|
||||
@@ -242,16 +356,18 @@ pub extern "C" fn regorus_engine_eval_query(
|
||||
engine: *mut RegorusEngine,
|
||||
query: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
let results = guard.eval_query(from_c_str(query)?, false)?;
|
||||
Ok(serde_json::to_string_pretty(&results)?)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
let results = guard.eval_query(from_c_str(query)?, false)?;
|
||||
Ok(serde_json::to_string_pretty(&results)?)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Evaluate specified rule.
|
||||
@@ -263,15 +379,17 @@ pub extern "C" fn regorus_engine_eval_rule(
|
||||
engine: *mut RegorusEngine,
|
||||
rule: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.eval_rule(from_c_str(rule)?)?.to_json_str()
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.eval_rule(from_c_str(rule)?)?.to_json_str()
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Enable/disable coverage.
|
||||
@@ -284,12 +402,14 @@ pub extern "C" fn regorus_engine_set_enable_coverage(
|
||||
engine: *mut RegorusEngine,
|
||||
enable: bool,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_enable_coverage(enable);
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_enable_coverage(enable);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Get coverage report.
|
||||
@@ -298,15 +418,17 @@ pub extern "C" fn regorus_engine_set_enable_coverage(
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "coverage")]
|
||||
pub extern "C" fn regorus_engine_get_coverage_report(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
Ok(serde_json::to_string_pretty(&guard.get_coverage_report()?)?)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
Ok(serde_json::to_string_pretty(&guard.get_coverage_report()?)?)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Enable/disable strict builtin errors.
|
||||
@@ -318,12 +440,14 @@ pub extern "C" fn regorus_engine_set_strict_builtin_errors(
|
||||
engine: *mut RegorusEngine,
|
||||
strict: bool,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_strict_builtin_errors(strict);
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_strict_builtin_errors(strict);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Get pretty printed coverage report.
|
||||
@@ -334,15 +458,17 @@ pub extern "C" fn regorus_engine_set_strict_builtin_errors(
|
||||
pub extern "C" fn regorus_engine_get_coverage_report_pretty(
|
||||
engine: *mut RegorusEngine,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_coverage_report()?.to_string_pretty()
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_coverage_report()?.to_string_pretty()
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear coverage data.
|
||||
@@ -351,12 +477,14 @@ pub extern "C" fn regorus_engine_get_coverage_report_pretty(
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "coverage")]
|
||||
pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_coverage_data();
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_coverage_data();
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether to gather output of print statements.
|
||||
@@ -368,12 +496,14 @@ pub extern "C" fn regorus_engine_set_gather_prints(
|
||||
engine: *mut RegorusEngine,
|
||||
enable: bool,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_gather_prints(enable);
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_gather_prints(enable);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Take all the gathered print statements.
|
||||
@@ -381,15 +511,17 @@ pub extern "C" fn regorus_engine_set_gather_prints(
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.take_prints
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
Ok(serde_json::to_string_pretty(&guard.take_prints()?)?)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
Ok(serde_json::to_string_pretty(&guard.take_prints()?)?)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get AST of policies.
|
||||
@@ -398,15 +530,17 @@ pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> Rego
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "ast")]
|
||||
pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_ast_as_json()
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_ast_as_json()
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the package names defined in each policy added to the engine.
|
||||
@@ -417,15 +551,18 @@ pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) ->
|
||||
pub extern "C" fn regorus_engine_get_policy_package_names(
|
||||
engine: *mut RegorusEngine,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_policy_package_names()?).map_err(anyhow::Error::msg)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_policy_package_names()?)
|
||||
.map_err(anyhow::Error::msg)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the parameters defined in each policy added to the engine.
|
||||
@@ -436,15 +573,18 @@ pub extern "C" fn regorus_engine_get_policy_package_names(
|
||||
pub extern "C" fn regorus_engine_get_policy_parameters(
|
||||
engine: *mut RegorusEngine,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_policy_parameters()?).map_err(anyhow::Error::msg)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_policy_parameters()?)
|
||||
.map_err(anyhow::Error::msg)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Enable/disable rego v1.
|
||||
@@ -455,16 +595,18 @@ pub extern "C" fn regorus_engine_set_rego_v0(
|
||||
engine: *mut RegorusEngine,
|
||||
enable: bool,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_rego_v0(enable);
|
||||
Ok(())
|
||||
}();
|
||||
match output {
|
||||
Ok(()) => RegorusResult::ok_void(),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_rego_v0(enable);
|
||||
Ok(())
|
||||
}();
|
||||
match output {
|
||||
Ok(()) => RegorusResult::ok_void(),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Compile a target-aware policy from the current engine state.
|
||||
@@ -476,37 +618,39 @@ pub extern "C" fn regorus_engine_set_rego_v0(
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_engine_compile_for_target(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
let engine = match to_ref(engine) {
|
||||
Ok(engine) => engine,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Failed to get engine reference: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let engine = match to_ref(engine) {
|
||||
Ok(engine) => engine,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Failed to get engine reference: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let mut guard = match engine.try_write() {
|
||||
Ok(guard) => guard,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to lock engine: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let mut guard = match engine.try_write() {
|
||||
Ok(guard) => guard,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to lock engine: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
match guard.compile_for_target() {
|
||||
Ok(compiled_policy) => {
|
||||
let wrapped_policy = RegorusCompiledPolicy { compiled_policy };
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
match guard.compile_for_target() {
|
||||
Ok(compiled_policy) => {
|
||||
let wrapped_policy = RegorusCompiledPolicy { compiled_policy };
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Failed to compile for target: {e}"),
|
||||
),
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Failed to compile for target: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Compile a policy with a specific entry point rule.
|
||||
@@ -520,23 +664,25 @@ pub extern "C" fn regorus_engine_compile_with_entrypoint(
|
||||
engine: *mut RegorusEngine,
|
||||
rule: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let result = || -> Result<RegorusCompiledPolicy> {
|
||||
let rule_str = from_c_str(rule)?;
|
||||
let rule_rc: regorus::Rc<str> = rule_str.into();
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
|
||||
Ok(RegorusCompiledPolicy { compiled_policy })
|
||||
}();
|
||||
with_unwind_guard(|| {
|
||||
let result = || -> Result<RegorusCompiledPolicy> {
|
||||
let rule_str = from_c_str(rule)?;
|
||||
let rule_rc: regorus::Rc<str> = rule_str.into();
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
|
||||
Ok(RegorusCompiledPolicy { compiled_policy })
|
||||
}();
|
||||
|
||||
match result {
|
||||
Ok(wrapped_policy) => {
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
match result {
|
||||
Ok(wrapped_policy) => {
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Failed to compile with entrypoint: {e}"),
|
||||
),
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Failed to compile with entrypoint: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,5 +12,6 @@ mod compiled_policy;
|
||||
mod effect_registry;
|
||||
mod engine;
|
||||
mod lock;
|
||||
mod panic_guard;
|
||||
mod schema_registry;
|
||||
mod target_registry;
|
||||
|
||||
159
bindings/ffi/src/panic_guard.rs
Normal file
159
bindings/ffi/src/panic_guard.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Minimal helpers for catching panics inside the FFI layer.
|
||||
//!
|
||||
//! These are not yet wired into the exported functions; they will
|
||||
//! be used once the integration work is complete.
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use crate::common::{RegorusResult, RegorusStatus};
|
||||
|
||||
use alloc::string::String;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
#[cfg(feature = "std")]
|
||||
use std::{
|
||||
backtrace::Backtrace,
|
||||
cell::RefCell,
|
||||
panic::{self, AssertUnwindSafe},
|
||||
};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
thread_local! {
|
||||
// Stashes the formatted panic + backtrace for whichever call last panicked on this thread.
|
||||
static PANIC_BACKTRACE: RefCell<Option<String>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
type PanicHook = dyn Fn(&panic::PanicHookInfo<'_>) + Sync + Send + 'static;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
/// RAII helper that installs a per-call panic hook and restores the prior hook on drop.
|
||||
struct PanicHookGuard {
|
||||
previous: Option<Box<PanicHook>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl PanicHookGuard {
|
||||
fn install() -> Self {
|
||||
// Remember whatever hook the embedding application already registered.
|
||||
let previous = panic::take_hook();
|
||||
PANIC_BACKTRACE.with(|slot| {
|
||||
slot.replace(None);
|
||||
});
|
||||
// Install our temporary hook so we can capture a backtrace for this invocation.
|
||||
panic::set_hook(Box::new(|info| {
|
||||
let backtrace = Backtrace::force_capture();
|
||||
PANIC_BACKTRACE.with(|slot| {
|
||||
slot.replace(Some(format!(
|
||||
"panic hook observed: {}\nbacktrace:\n{:#?}",
|
||||
info, backtrace
|
||||
)));
|
||||
});
|
||||
}));
|
||||
Self {
|
||||
previous: Some(previous),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Drop for PanicHookGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(previous) = self.previous.take() {
|
||||
// Restore the original panic hook before we return control to the host.
|
||||
panic::set_hook(previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static POISONED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Result of attempting to run `f` while guarding against unwinding.
|
||||
pub(crate) enum GuardResult<T> {
|
||||
/// Closure completed successfully.
|
||||
Success(T),
|
||||
/// Closure panicked; contains a best-effort string payload.
|
||||
Panic(String),
|
||||
}
|
||||
|
||||
pub(crate) fn with_unwind_guard<F>(f: F) -> RegorusResult
|
||||
where
|
||||
F: FnOnce() -> RegorusResult,
|
||||
{
|
||||
if is_poisoned() {
|
||||
return poisoned_result();
|
||||
}
|
||||
|
||||
// The closure passed across this boundary closes over raw pointers and lock guards.
|
||||
// These types are not unwind safe by default and may become poisoned if a panic occurs.
|
||||
// We therefore use AssertUnwindSafe to get the compiler to accept the closure.
|
||||
// Upon unwind, we mark regorus as poisoned and disallow further use.
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
let outcome = {
|
||||
let _hook_guard = PanicHookGuard::install();
|
||||
match panic::catch_unwind(AssertUnwindSafe(f)) {
|
||||
Ok(value) => GuardResult::Success(value),
|
||||
Err(payload) => GuardResult::Panic(panic_message_to_string(payload)),
|
||||
}
|
||||
};
|
||||
finalize(outcome)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
return finalize(GuardResult::Success(f()));
|
||||
}
|
||||
|
||||
fn finalize(outcome: GuardResult<RegorusResult>) -> RegorusResult {
|
||||
match outcome {
|
||||
GuardResult::Success(result) => result,
|
||||
GuardResult::Panic(message) => {
|
||||
trip_poison();
|
||||
RegorusResult::err_with_message(RegorusStatus::Panic, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
fn panic_message_to_string(payload: Box<dyn core::any::Any + Send + 'static>) -> String {
|
||||
let mut message = if let Some(s) = payload.downcast_ref::<&str>() {
|
||||
(*s).into()
|
||||
} else if let Some(s) = payload.downcast_ref::<String>() {
|
||||
s.clone()
|
||||
} else {
|
||||
String::from("regorus encountered panic")
|
||||
};
|
||||
|
||||
if let Some(backtrace) = take_panic_backtrace() {
|
||||
message.push('\n');
|
||||
message.push_str(&backtrace);
|
||||
}
|
||||
|
||||
message
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
fn take_panic_backtrace() -> Option<String> {
|
||||
PANIC_BACKTRACE.with(|slot| slot.borrow_mut().take())
|
||||
}
|
||||
|
||||
fn poisoned_result() -> RegorusResult {
|
||||
RegorusResult::err_with_message(
|
||||
RegorusStatus::Poisoned,
|
||||
String::from("regorus is poisoned after a previous panic"),
|
||||
)
|
||||
}
|
||||
|
||||
fn trip_poison() {
|
||||
POISONED.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
pub(crate) fn is_poisoned() -> bool {
|
||||
POISONED.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub(crate) fn reset_poison() {
|
||||
POISONED.store(false, Ordering::Release);
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#![cfg(feature = "azure_policy")]
|
||||
|
||||
use crate::common::{from_c_str, RegorusResult, RegorusStatus};
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use regorus::{registry::schemas, Schema};
|
||||
|
||||
use std::os::raw::c_char;
|
||||
@@ -32,45 +33,45 @@ pub extern "C" fn regorus_resource_schema_register(
|
||||
name: *const c_char,
|
||||
schema_json: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let schema_str = match from_c_str(schema_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid schema JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let schema_str = match from_c_str(schema_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid schema JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Parse schema from JSON
|
||||
let schema = match Schema::from_json_str(&schema_str) {
|
||||
Ok(schema) => schema,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse schema JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let schema = match Schema::from_json_str(&schema_str) {
|
||||
Ok(schema) => schema,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse schema JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Register the schema
|
||||
match schemas::resource::register(schema_name, schema.into()) {
|
||||
Ok(()) => RegorusResult::ok_pointer(std::ptr::null_mut()),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to register schema: {e}"),
|
||||
),
|
||||
}
|
||||
match schemas::resource::register(schema_name, schema.into()) {
|
||||
Ok(()) => RegorusResult::ok_pointer(std::ptr::null_mut()),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to register schema: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a resource schema with the given name exists.
|
||||
@@ -86,18 +87,20 @@ pub extern "C" fn regorus_resource_schema_register(
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_resource_schema_contains(name: *const c_char) -> RegorusResult {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let contains = schemas::resource::contains(&schema_name);
|
||||
RegorusResult::ok_bool(contains)
|
||||
let contains = schemas::resource::contains(&schema_name);
|
||||
RegorusResult::ok_bool(contains)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the number of registered resource schemas.
|
||||
@@ -107,8 +110,10 @@ pub extern "C" fn regorus_resource_schema_contains(name: *const c_char) -> Regor
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_resource_schema_len() -> RegorusResult {
|
||||
let count = schemas::resource::len();
|
||||
RegorusResult::ok_int(count as i64)
|
||||
with_unwind_guard(|| {
|
||||
let count = schemas::resource::len();
|
||||
RegorusResult::ok_int(count as i64)
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if the resource schema registry is empty.
|
||||
@@ -118,8 +123,10 @@ pub extern "C" fn regorus_resource_schema_len() -> RegorusResult {
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_resource_schema_is_empty() -> RegorusResult {
|
||||
let is_empty = schemas::resource::is_empty();
|
||||
RegorusResult::ok_bool(is_empty)
|
||||
with_unwind_guard(|| {
|
||||
let is_empty = schemas::resource::is_empty();
|
||||
RegorusResult::ok_bool(is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
/// List all registered resource schema names as a JSON array.
|
||||
@@ -129,14 +136,16 @@ pub extern "C" fn regorus_resource_schema_is_empty() -> RegorusResult {
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_resource_schema_list_names() -> RegorusResult {
|
||||
let names = schemas::resource::list_names();
|
||||
match serde_json::to_string(&names) {
|
||||
Ok(json_str) => RegorusResult::ok_string(json_str),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to serialize schema names to JSON: {e}"),
|
||||
),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let names = schemas::resource::list_names();
|
||||
match serde_json::to_string(&names) {
|
||||
Ok(json_str) => RegorusResult::ok_string(json_str),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to serialize schema names to JSON: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove a resource schema by name.
|
||||
@@ -152,18 +161,20 @@ pub extern "C" fn regorus_resource_schema_list_names() -> RegorusResult {
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_resource_schema_remove(name: *const c_char) -> RegorusResult {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let removed = schemas::resource::remove(&schema_name).is_some();
|
||||
RegorusResult::ok_bool(removed)
|
||||
let removed = schemas::resource::remove(&schema_name).is_some();
|
||||
RegorusResult::ok_bool(removed)
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear all resource schemas from the registry.
|
||||
@@ -173,6 +184,8 @@ pub extern "C" fn regorus_resource_schema_remove(name: *const c_char) -> Regorus
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_resource_schema_clear() -> RegorusResult {
|
||||
schemas::resource::clear();
|
||||
RegorusResult::ok_pointer(std::ptr::null_mut())
|
||||
with_unwind_guard(|| {
|
||||
schemas::resource::clear();
|
||||
RegorusResult::ok_pointer(std::ptr::null_mut())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#![cfg(feature = "azure_policy")]
|
||||
|
||||
use crate::common::*;
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use anyhow::Result;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
@@ -16,12 +17,14 @@ use std::os::raw::c_char;
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_register_target_from_json(target_json: *const c_char) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let target_str = from_c_str(target_json)?;
|
||||
let target = regorus::Target::from_json_str(&target_str)?;
|
||||
regorus::registry::targets::register(regorus::Rc::new(target))?;
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let target_str = from_c_str(target_json)?;
|
||||
let target = regorus::Target::from_json_str(&target_str)?;
|
||||
regorus::registry::targets::register(regorus::Rc::new(target))?;
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a target is registered.
|
||||
@@ -36,31 +39,35 @@ pub extern "C" fn regorus_register_target_from_json(target_json: *const c_char)
|
||||
/// The name parameter must be a valid null-terminated UTF-8 string.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_target_registry_contains(name: *const c_char) -> RegorusResult {
|
||||
let target_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid target name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let target_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid target name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let contains = regorus::registry::targets::contains(&target_name);
|
||||
RegorusResult::ok_bool(contains)
|
||||
let contains = regorus::registry::targets::contains(&target_name);
|
||||
RegorusResult::ok_bool(contains)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a list of all registered target names as JSON array.
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_target_registry_list_names() -> RegorusResult {
|
||||
let names = regorus::registry::targets::list_names();
|
||||
let output = serde_json::to_string_pretty(&names).map_err(anyhow::Error::msg);
|
||||
with_unwind_guard(|| {
|
||||
let names = regorus::registry::targets::list_names();
|
||||
let output = serde_json::to_string_pretty(&names).map_err(anyhow::Error::msg);
|
||||
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove a target from the registry by name.
|
||||
@@ -69,19 +76,23 @@ pub extern "C" fn regorus_target_registry_list_names() -> RegorusResult {
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_target_registry_remove(name: *const c_char) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let name_str = from_c_str(name)?;
|
||||
regorus::registry::targets::remove(&name_str);
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let name_str = from_c_str(name)?;
|
||||
regorus::registry::targets::remove(&name_str);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear all targets from the registry.
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_target_registry_clear() -> RegorusResult {
|
||||
regorus::registry::targets::clear();
|
||||
RegorusResult::ok_void()
|
||||
with_unwind_guard(|| {
|
||||
regorus::registry::targets::clear();
|
||||
RegorusResult::ok_void()
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the number of registered targets.
|
||||
@@ -91,8 +102,10 @@ pub extern "C" fn regorus_target_registry_clear() -> RegorusResult {
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_target_registry_len() -> RegorusResult {
|
||||
let count = regorus::registry::targets::len();
|
||||
RegorusResult::ok_int(count as i64)
|
||||
with_unwind_guard(|| {
|
||||
let count = regorus::registry::targets::len();
|
||||
RegorusResult::ok_int(count as i64)
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if the target registry is empty.
|
||||
@@ -102,6 +115,8 @@ pub extern "C" fn regorus_target_registry_len() -> RegorusResult {
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_target_registry_is_empty() -> RegorusResult {
|
||||
let is_empty = regorus::registry::targets::is_empty();
|
||||
RegorusResult::ok_bool(is_empty)
|
||||
with_unwind_guard(|| {
|
||||
let is_empty = regorus::registry::targets::is_empty();
|
||||
RegorusResult::ok_bool(is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user