mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
* fix(ffi): eliminate aliasing UB via to_shared_ref migration Add to_shared_ref() helper that creates &T (shared reference) from raw pointers instead of &mut T. This eliminates undefined behavior caused by violating Rust's aliasing invariant when C# SafeHandle permits concurrent FFI calls on the same handle. With &mut T, the compiler may assume exclusive (noalias) access and reorder or elide reads/writes — a miscompilation risk when another thread holds a reference to the same object. Switching to &T removes that assumption; actual mutation is mediated by the interior RwLock inside Handle<T>, which is the sole synchronization mechanism. Migrated sites: - rvm.rs: 20 non-drop call sites - engine.rs: 30 non-drop call sites + with_unwind_guard for timer fns - compiled_policy.rs: 2 call sites - Fix null-data UB in regorus_program_deserialize_binary Drop paths retain to_ref() where exclusive access is guaranteed by the caller contract (preventing use-after-free). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(ffi): add Azure Policy JSON compilation FFI and C# bindings - AliasRegistry builder pattern: RegorusAliasRegistryBuilder (mutable, single-threaded) + RegorusAliasRegistry (immutable, Arc-wrapped) - Azure Policy JSON compilation: regorus_compile_azure_policy_rule and regorus_compile_azure_policy_definition with alias registry support - regorus_rvm_set_context for host-supplied ambient data - C# AliasRegistryBuilder and AliasRegistry classes with convenience factories (FromJson, FromManifest, Empty) - C# AzurePolicyCompiler static class for policy rule/definition compilation - Compile functions take *const RegorusAliasRegistry (read-only via to_shared_ref for concurrent compilation safety) - Fix pre-existing clippy warnings across multiple crates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
184 lines
7.7 KiB
C#
184 lines
7.7 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using Regorus.Internal;
|
|
|
|
#nullable enable
|
|
namespace Regorus
|
|
{
|
|
/// <summary>
|
|
/// Provides static methods for compiling Azure Policy JSON definitions
|
|
/// into RVM programs that can be executed by <see cref="Rvm"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// This class bridges the gap between Azure Policy JSON (the native
|
|
/// Azure policy language with <c>policyRule</c>, <c>field</c>,
|
|
/// <c>equals</c>, etc.) and Regorus's RVM execution engine.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// <b>Typical workflow:</b>
|
|
/// </para>
|
|
/// <list type="number">
|
|
/// <item>Load alias definitions with <see cref="AliasRegistryBuilder"/> and freeze them into an <see cref="AliasRegistry"/>.</item>
|
|
/// <item>Normalize the ARM resource via <see cref="AliasRegistry.NormalizeAndWrap"/>.</item>
|
|
/// <item>Compile the JSON policyRule with <see cref="CompilePolicyRule"/> or the
|
|
/// full definition with <see cref="CompilePolicyDefinition"/>.</item>
|
|
/// <item>Execute the resulting <see cref="Program"/> in an <see cref="Rvm"/>
|
|
/// instance with the normalized input.</item>
|
|
/// </list>
|
|
///
|
|
/// <para>
|
|
/// <b>Context-dependent policies:</b> Policies that use context functions
|
|
/// such as <c>subscription()</c>, <c>resourceGroup()</c>, or
|
|
/// <c>requestContext()</c> require the VM context to be set separately via
|
|
/// <see cref="Rvm.SetContextJson"/> before execution. The context JSON
|
|
/// returned by <see cref="AliasRegistry.NormalizeAndWrap"/> is passed as
|
|
/// <c>input.context</c> but is <b>not</b> automatically wired into the VM's
|
|
/// ambient context — the caller must do both:
|
|
/// <c>vm.SetInputJson(envelope)</c> and <c>vm.SetContextJson(contextJson)</c>.
|
|
/// </para>
|
|
/// </remarks>
|
|
public static unsafe class AzurePolicyCompiler
|
|
{
|
|
/// <summary>
|
|
/// Compile an Azure Policy JSON policy rule into an RVM <see cref="Program"/>.
|
|
/// </summary>
|
|
/// <param name="aliasRegistry">
|
|
/// Alias registry for resolving fully-qualified alias names in field
|
|
/// references. Pass <c>null</c> if no alias resolution is needed.
|
|
/// <para>
|
|
/// <b>Warning:</b> When <c>null</c>, alias field references compile as raw
|
|
/// property paths and will silently produce incorrect evaluation results for
|
|
/// policies that use aliases. Modify/Append effect policies will also skip
|
|
/// the compile-time modifiability validation. Only pass <c>null</c> when the
|
|
/// policy is known to contain no alias references (e.g. simple type/location
|
|
/// checks or unit-test scenarios).
|
|
/// </para>
|
|
/// </param>
|
|
/// <param name="policyRuleJson">
|
|
/// JSON string containing the policyRule object, e.g.
|
|
/// <c>{ "if": { "field": "type", "equals": "..." }, "then": { "effect": "deny" } }</c>
|
|
/// </param>
|
|
/// <returns>
|
|
/// A compiled <see cref="Program"/> ready to be loaded into an
|
|
/// <see cref="Rvm"/> instance.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// Thrown when <paramref name="policyRuleJson"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="Exception">
|
|
/// Thrown when parsing or compilation fails.
|
|
/// </exception>
|
|
public static Program CompilePolicyRule(AliasRegistry? aliasRegistry, string policyRuleJson)
|
|
{
|
|
if (policyRuleJson is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(policyRuleJson));
|
|
}
|
|
|
|
return Utf8Marshaller.WithUtf8(policyRuleJson, rulePtr =>
|
|
{
|
|
if (aliasRegistry is null)
|
|
{
|
|
var result = API.regorus_compile_azure_policy_rule(
|
|
null, (byte*)rulePtr);
|
|
return GetProgramResult(result);
|
|
}
|
|
else
|
|
{
|
|
return aliasRegistry.UseHandleForInterop(regPtr =>
|
|
{
|
|
var result = API.regorus_compile_azure_policy_rule(
|
|
(RegorusAliasRegistry*)regPtr, (byte*)rulePtr);
|
|
return GetProgramResult(result);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compile a full Azure Policy definition JSON into an RVM <see cref="Program"/>.
|
|
/// </summary>
|
|
/// <param name="aliasRegistry">
|
|
/// Alias registry for resolving fully-qualified alias names in field
|
|
/// references. Pass <c>null</c> if no alias resolution is needed.
|
|
/// <para>
|
|
/// <b>Warning:</b> When <c>null</c>, alias field references compile as raw
|
|
/// property paths and will silently produce incorrect evaluation results for
|
|
/// policies that use aliases. Modify/Append effect policies will also skip
|
|
/// the compile-time modifiability validation. Only pass <c>null</c> when the
|
|
/// policy is known to contain no alias references (e.g. simple type/location
|
|
/// checks or unit-test scenarios).
|
|
/// </para>
|
|
/// </param>
|
|
/// <param name="policyDefinitionJson">
|
|
/// JSON string containing the full policy definition, which includes
|
|
/// <c>policyRule</c>, <c>parameters</c>, <c>displayName</c>, etc.
|
|
/// Accepted in both wrapped and unwrapped forms.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A compiled <see cref="Program"/> ready to be loaded into an
|
|
/// <see cref="Rvm"/> instance.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// Thrown when <paramref name="policyDefinitionJson"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="Exception">
|
|
/// Thrown when parsing or compilation fails.
|
|
/// </exception>
|
|
public static Program CompilePolicyDefinition(AliasRegistry? aliasRegistry, string policyDefinitionJson)
|
|
{
|
|
if (policyDefinitionJson is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(policyDefinitionJson));
|
|
}
|
|
|
|
return Utf8Marshaller.WithUtf8(policyDefinitionJson, defnPtr =>
|
|
{
|
|
if (aliasRegistry is null)
|
|
{
|
|
var result = API.regorus_compile_azure_policy_definition(
|
|
null, (byte*)defnPtr);
|
|
return GetProgramResult(result);
|
|
}
|
|
else
|
|
{
|
|
return aliasRegistry.UseHandleForInterop(regPtr =>
|
|
{
|
|
var result = API.regorus_compile_azure_policy_definition(
|
|
(RegorusAliasRegistry*)regPtr, (byte*)defnPtr);
|
|
return GetProgramResult(result);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
private static Program GetProgramResult(RegorusResult result)
|
|
{
|
|
try
|
|
{
|
|
if (result.status != RegorusStatus.Ok)
|
|
{
|
|
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
|
throw result.status.CreateException(message);
|
|
}
|
|
|
|
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
|
|
{
|
|
throw new Exception("Expected program pointer but got different data type");
|
|
}
|
|
|
|
var handle = RegorusProgramHandle.FromPointer((IntPtr)result.pointer_value);
|
|
return new Program(handle);
|
|
}
|
|
finally
|
|
{
|
|
API.regorus_result_drop(result);
|
|
}
|
|
}
|
|
}
|
|
}
|