Skip to content

Commit 86b4a27

Browse files
anakrishCopilot
andauthored
fix(ffi): eliminate aliasing UB + add Azure Policy JSON compilation FFI (#727)
* 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>
1 parent 5467cd9 commit 86b4a27

26 files changed

Lines changed: 1963 additions & 292 deletions

bindings/csharp/Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22
<PropertyGroup>
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
4-
<RegorusPackageVersion>0.10.0</RegorusPackageVersion>
4+
<RegorusPackageVersion>0.10.1</RegorusPackageVersion>
55
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
66
</PropertyGroup>
77

bindings/csharp/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,76 @@ const string ContextJson = """
150150
var allowed = RbacEngine.EvaluateCondition(Condition, ContextJson);
151151
Console.WriteLine($"RBAC condition allowed: {allowed}");
152152
```
153+
154+
## Azure Policy JSON Evaluation
155+
156+
Compile and evaluate Azure Policy JSON `policyRule` definitions directly — no Rego translation required.
157+
The `AzurePolicyCompiler` compiles JSON policy rules into RVM programs that can be executed with the `Rvm` engine.
158+
159+
```csharp
160+
using Regorus;
161+
162+
// 1. Load alias definitions for the resource provider
163+
const string AliasesJson = """
164+
[{
165+
"namespace": "Microsoft.Storage",
166+
"resourceTypes": [{
167+
"resourceType": "storageAccounts",
168+
"aliases": [{
169+
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
170+
"defaultPath": "properties.supportsHttpsTrafficOnly",
171+
"paths": []
172+
}]
173+
}]
174+
}]
175+
""";
176+
177+
using var registry = AliasRegistry.FromJson(AliasesJson);
178+
179+
// 2. Compile a JSON policy rule (the native Azure Policy language)
180+
const string PolicyRule = """
181+
{
182+
"if": {
183+
"allOf": [
184+
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
185+
{ "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly", "equals": false }
186+
]
187+
},
188+
"then": { "effect": "deny" }
189+
}
190+
""";
191+
192+
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, PolicyRule);
193+
194+
// 3. Normalize an ARM resource and evaluate
195+
var armResource = """
196+
{
197+
"type": "Microsoft.Storage/storageAccounts",
198+
"name": "mystorage",
199+
"properties": { "supportsHttpsTrafficOnly": false }
200+
}
201+
""";
202+
var envelope = registry.NormalizeAndWrap(armResource);
203+
204+
using var vm = new Rvm();
205+
vm.LoadProgram(program);
206+
vm.SetInputJson(envelope!);
207+
208+
var result = vm.ExecuteEntryPoint("main");
209+
// result: {"effect": "deny"} for non-compliant, "<undefined>" for compliant
210+
Console.WriteLine($"Policy result: {result}");
211+
```
212+
213+
**Context-dependent policies:** If your policy uses context functions like
214+
`subscription()`, `resourceGroup()`, or `requestContext()`, you must also set
215+
the VM context separately:
216+
217+
```csharp
218+
// The context JSON from NormalizeAndWrap is in the input envelope,
219+
// but must also be provided to the VM's ambient context:
220+
vm.SetContextJson(contextJson);
221+
```
222+
223+
You can also compile full policy definitions (with parameters) using
224+
`AzurePolicyCompiler.CompilePolicyDefinition()`. See
225+
`bindings/csharp/Regorus.Tests/AzurePolicyCompilerTests.cs` for comprehensive examples.

bindings/csharp/Regorus.Tests/AliasRegistryTests.cs

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,31 +43,28 @@ public class AliasRegistryTests
4343
[TestMethod]
4444
public void Create_and_dispose_succeeds()
4545
{
46-
using var registry = new AliasRegistry();
46+
using var registry = AliasRegistry.Empty();
4747
Assert.AreEqual(0, registry.Length);
4848
}
4949

5050
[TestMethod]
5151
public void LoadJson_populates_registry()
5252
{
53-
using var registry = new AliasRegistry();
54-
registry.LoadJson(AliasesJson);
53+
using var registry = AliasRegistry.FromJson(AliasesJson);
5554
Assert.AreEqual(1, registry.Length);
5655
}
5756

5857
[TestMethod]
5958
public void LoadManifest_populates_registry()
6059
{
61-
using var registry = new AliasRegistry();
62-
registry.LoadManifest(ManifestJson);
60+
using var registry = AliasRegistry.FromManifest(ManifestJson);
6361
Assert.AreEqual(1, registry.Length);
6462
}
6563

6664
[TestMethod]
6765
public void NormalizeAndWrap_produces_envelope()
6866
{
69-
using var registry = new AliasRegistry();
70-
registry.LoadJson(AliasesJson);
67+
using var registry = AliasRegistry.FromJson(AliasesJson);
7168

7269
var resource = @"{
7370
""name"": ""acct1"",
@@ -93,8 +90,7 @@ public void NormalizeAndWrap_produces_envelope()
9390
[TestMethod]
9491
public void NormalizeAndWrap_with_context_and_parameters()
9592
{
96-
using var registry = new AliasRegistry();
97-
registry.LoadJson(AliasesJson);
93+
using var registry = AliasRegistry.FromJson(AliasesJson);
9894

9995
var resource = @"{
10096
""name"": ""acct1"",
@@ -115,8 +111,7 @@ public void NormalizeAndWrap_with_context_and_parameters()
115111
[TestMethod]
116112
public void Denormalize_restores_properties()
117113
{
118-
using var registry = new AliasRegistry();
119-
registry.LoadJson(AliasesJson);
114+
using var registry = AliasRegistry.FromJson(AliasesJson);
120115

121116
var normalized = @"{
122117
""name"": ""acct1"",
@@ -137,8 +132,7 @@ public void Denormalize_restores_properties()
137132
[TestMethod]
138133
public void Round_trip_normalize_then_denormalize()
139134
{
140-
using var registry = new AliasRegistry();
141-
registry.LoadJson(AliasesJson);
135+
using var registry = AliasRegistry.FromJson(AliasesJson);
142136

143137
var resource = @"{
144138
""name"": ""acct1"",
@@ -166,8 +160,7 @@ public void Round_trip_normalize_then_denormalize()
166160
[TestMethod]
167161
public void DataPlane_manifest_normalize()
168162
{
169-
using var registry = new AliasRegistry();
170-
registry.LoadManifest(ManifestJson);
163+
using var registry = AliasRegistry.FromManifest(ManifestJson);
171164

172165
var resource = @"{
173166
""type"": ""Microsoft.KeyVault.Data/vaults/certificates"",
@@ -185,7 +178,7 @@ public void DataPlane_manifest_normalize()
185178
[ExpectedException(typeof(InvalidOperationException))]
186179
public void LoadJson_invalid_throws()
187180
{
188-
using var registry = new AliasRegistry();
189-
registry.LoadJson("not valid json");
181+
using var builder = new AliasRegistryBuilder();
182+
builder.LoadJson("not valid json");
190183
}
191184
}

0 commit comments

Comments
 (0)