lib/machine/src/instance/adversarial/test.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const core = @import("machine_instance_core");
  2 const os = @import("os");
  3 const std = @import("std");
  4 const admission = @import("../../admission/root.zig");
  5 const instance = @import("../root.zig");
  6 const profile = @import("../../profile/root.zig");
  7 const test_support = @import("../fixture/root.zig");
  8 
  9 const fixture = test_support.fixture;
 10 
 11 const real_k0_elf = @embedFile("machine-k0-elf");
 12 const real_k0_manifest = @embedFile("machine-k0-manifest");
 13 const layout = core.layout;
 14 
 15 const instance_alias_bytes = @max(
 16     @sizeOf(instance.Instance),
 17     @sizeOf(instance.EventBatch),
 18 );
 19 const instance_alias_alignment = @max(
 20     @alignOf(instance.Instance),
 21     @alignOf(instance.EventBatch),
 22 );
 23 
 24 test "EventBatch atomic drain rejects corrupt second K0 input event" {
 25     var storage = instance.Storage.init();
 26     var machine = try startInput(&storage);
 27     defer machine.deinit();
 28     try corruptSecondInputEvent(&machine);
 29     var output = outputSentinel();
 30     try expectRejected(
 31         error.EventReceiptMismatch,
 32         &machine,
 33         &storage,
 34         &output,
 35     );
 36 }
 37 
 38 test "EventBatch atomic drain rejects corrupt third K0 input event" {
 39     var storage = instance.Storage.init();
 40     var machine = try startInput(&storage);
 41     defer machine.deinit();
 42     try corruptThirdInputEvent(&machine);
 43     var output = outputSentinel();
 44     try expectRejected(
 45         error.EventReceiptMismatch,
 46         &machine,
 47         &storage,
 48         &output,
 49     );
 50 }
 51 
 52 test "EventBatch atomic drain rejects stale K0 quiescence generation" {
 53     var storage = instance.Storage.init();
 54     var machine = try startInput(&storage);
 55     defer machine.deinit();
 56     try corruptFinalInputEvent(&machine);
 57     var output = outputSentinel();
 58     try expectRejected(
 59         error.EventReceiptMismatch,
 60         &machine,
 61         &storage,
 62         &output,
 63     );
 64 }
 65 
 66 test "EventBatch atomic drain rejects shifted K0 request frontiers at quiescence" {
 67     var storage = instance.Storage.init();
 68     var machine = try startInput(&storage);
 69     defer machine.deinit();
 70     try shiftSettledRequestFrontiers(&machine);
 71     var output = outputSentinel();
 72     try expectRejected(
 73         error.EventReceiptMismatch,
 74         &machine,
 75         &storage,
 76         &output,
 77     );
 78 }
 79 
 80 test "EventBatch atomic drain rejects a missing K0 activation event" {
 81     var storage = instance.Storage.init();
 82     var machine = try startActivation(&storage);
 83     defer machine.deinit();
 84     try hideLastEvent(&machine);
 85     var output = outputSentinel();
 86     try expectRejected(
 87         error.EventReceiptMismatch,
 88         &machine,
 89         &storage,
 90         &output,
 91     );
 92 }
 93 
 94 test "EventBatch atomic drain rejects an extra K0 activation event" {
 95     var storage = instance.Storage.init();
 96     var machine = try startActivation(&storage);
 97     defer machine.deinit();
 98     try appendLastEvent(&machine);
 99     var output = outputSentinel();
100     try expectRejected(
101         error.EventReceiptMismatch,
102         &machine,
103         &storage,
104         &output,
105     );
106 }
107 
108 test "EventBatch atomic drain rejects a missing K0 input event" {
109     var storage = instance.Storage.init();
110     var machine = try startInput(&storage);
111     defer machine.deinit();
112     try hideLastEvent(&machine);
113     var output = outputSentinel();
114     try expectRejected(
115         error.EventReceiptMismatch,
116         &machine,
117         &storage,
118         &output,
119     );
120 }
121 
122 test "EventBatch atomic drain rejects an extra K0 input event" {
123     var storage = instance.Storage.init();
124     var machine = try startInput(&storage);
125     defer machine.deinit();
126     try appendLastEvent(&machine);
127     var output = outputSentinel();
128     try expectRejected(
129         error.EventReceiptMismatch,
130         &machine,
131         &storage,
132         &output,
133     );
134 }
135 
136 test "EventBatch atomic drain rejects Instance Storage and RAM aliases" {
137     var storage = instance.Storage.init();
138     var alias: [instance_alias_bytes]u8 align(instance_alias_alignment) =
139         @splat(0);
140     const machine: *instance.Instance = @ptrCast(&alias);
141     machine.* = try startActivation(&storage);
142     defer machine.deinit();
143 
144     const instance_output: *instance.EventBatch = @ptrCast(&alias);
145     try expectRejected(
146         error.OutputAliasesInstance,
147         machine,
148         &storage,
149         instance_output,
150     );
151     const storage_output: *instance.EventBatch = @ptrCast(&storage.bytes);
152     try expectRejected(
153         error.OutputAliasesOwner,
154         machine,
155         &storage,
156         storage_output,
157     );
158     const ram_output: *instance.EventBatch = @ptrCast(&fixture.ram);
159     try expectRejected(
160         error.OutputAliasesRam,
161         machine,
162         &storage,
163         ram_output,
164     );
165 }
166 
167 fn startActivation(storage: *instance.Storage) !instance.Instance {
168     const execution = try os.boot.kernel.manifest.parse(real_k0_manifest);
169     var machine = switch (instance.Instance.init(
170         storage,
171         &fixture.ram,
172         realK0Input(execution),
173     )) {
174         .ready => |value| value,
175         .unavailable => return error.UnexpectedBackendUnavailable,
176         .rejected => |failure| return failure,
177     };
178     errdefer machine.deinit();
179     const exit = try machine.run();
180     try std.testing.expectEqual(.doorbell, std.meta.activeTag(exit));
181     try std.testing.expectEqual(
182         os.abi.channel.DoorbellCode.ready,
183         exit.doorbell.code,
184     );
185     try std.testing.expectEqual(
186         instance.RunPhase.draining_activation,
187         machine.phase(),
188     );
189     return machine;
190 }
191 
192 fn startInput(storage: *instance.Storage) !instance.Instance {
193     var machine = try startActivation(storage);
194     errdefer machine.deinit();
195     var activation: instance.EventBatch = undefined;
196     try machine.takeEvents(&activation);
197     const admitted = try admission.prepare(
198         try machine.admissionBasis(),
199         try admission.terminal(0, os.k0.request_bytes),
200     );
201     const delivery = try admission.bindDelivery(
202         admitted,
203         @splat(0x88),
204         realK0Fence(),
205     );
206     try machine.deliverAdmitted(&delivery);
207     const exit = try machine.run();
208     try std.testing.expectEqual(.doorbell, std.meta.activeTag(exit));
209     try std.testing.expectEqual(
210         os.abi.channel.DoorbellCode.quiescent,
211         exit.doorbell.code,
212     );
213     try std.testing.expectEqual(
214         instance.RunPhase.draining_input,
215         machine.phase(),
216     );
217     return machine;
218 }
219 
220 fn corruptSecondInputEvent(machine: *instance.Instance) !void {
221     const frame = try inputEvent(machine, 1);
222     const decoded = try os.abi.decodeEvent(frame);
223     if (std.meta.activeTag(decoded.value) != .terminal_bytes) {
224         return error.TestExpectedTerminalEvent;
225     }
226     var value = decoded.value.terminal_bytes;
227     value.offset = std.math.add(u64, value.offset, 1) catch unreachable;
228     try os.abi.encodeTerminalEvent(
229         decoded.header.fence,
230         decoded.header.sequence,
231         value,
232         frame,
233     );
234 }
235 
236 fn corruptThirdInputEvent(machine: *instance.Instance) !void {
237     const frame = try inputEvent(machine, 2);
238     const decoded = try os.abi.decodeEvent(frame);
239     if (std.meta.activeTag(decoded.value) != .block_root) {
240         return error.TestExpectedBlockRootEvent;
241     }
242     var value = decoded.value.block_root;
243     value.generation = std.math.add(u64, value.generation, 1) catch unreachable;
244     try os.abi.encodeBlockRootEvent(
245         decoded.header.fence,
246         decoded.header.sequence,
247         value,
248         frame,
249     );
250 }
251 
252 fn corruptFinalInputEvent(machine: *instance.Instance) !void {
253     const frame = try inputEvent(machine, 3);
254     const decoded = try os.abi.decodeEvent(frame);
255     if (std.meta.activeTag(decoded.value) != .quiescent) {
256         return error.TestExpectedQuiescenceEvent;
257     }
258     var value = decoded.value.quiescent;
259     value.capability_generation += 1;
260     try os.abi.encodeQuiescentEvent(
261         decoded.header.fence,
262         decoded.header.sequence,
263         value,
264         frame,
265     );
266 }
267 
268 fn inputEvent(
269     machine: *instance.Instance,
270     offset: u64,
271 ) !*os.abi.MessageWire {
272     const events = layout.eventRing(machine.ram);
273     const frontiers = try os.abi.EventRing.frontiers(events, realK0Fence());
274     if (frontiers.count() != os.k0.events_per_terminal_input) {
275         return error.TestExpectedInputEvents;
276     }
277     const index: usize = @intCast(
278         (frontiers.consumed + offset) % os.abi.ring.capacity,
279     );
280     return &events.slots[index];
281 }
282 
283 fn hideLastEvent(machine: *instance.Instance) !void {
284     const events = layout.eventRing(machine.ram);
285     const frontiers = try os.abi.EventRing.frontiers(events, realK0Fence());
286     if (frontiers.count() == 0) return error.TestExpectedEvents;
287     os.abi.wire.write64(
288         events.header[32..40],
289         frontiers.produced - 1,
290     );
291 }
292 
293 fn appendLastEvent(machine: *instance.Instance) !void {
294     const events = layout.eventRing(machine.ram);
295     const frontiers = try os.abi.EventRing.frontiers(events, realK0Fence());
296     if (frontiers.count() == 0) return error.TestExpectedEvents;
297     var extra: os.abi.MessageWire = undefined;
298     try os.abi.encodeTerminalEvent(
299         realK0Fence(),
300         frontiers.produced + 1,
301         .{ .offset = 0, .bytes = "extra" },
302         &extra,
303     );
304     try os.abi.EventRing.push(
305         events,
306         realK0Fence(),
307         &extra,
308     );
309 }
310 
311 fn shiftSettledRequestFrontiers(machine: *instance.Instance) !void {
312     const requests = layout.requestRing(machine.ram);
313     const frontiers = try os.abi.RequestRing.frontiers(
314         requests,
315         realK0Fence(),
316     );
317     if (frontiers.consumed != 1 or frontiers.produced != 1) {
318         return error.TestExpectedSettledRequest;
319     }
320     os.abi.wire.write64(requests.header[24..32], 2);
321     os.abi.wire.write64(requests.header[32..40], 2);
322 }
323 
324 fn expectRejected(
325     comptime expected: anyerror,
326     machine: *instance.Instance,
327     storage: *instance.Storage,
328     output: *instance.EventBatch,
329 ) !void {
330     const output_before = output.*;
331     const machine_before = machine.*;
332     const storage_before = storage.*;
333     const requests_before = layout.requestRing(machine.ram).*;
334     const events_before = layout.eventRing(machine.ram).*;
335     const phase_before = machine.phase();
336     try std.testing.expectError(
337         error.QuiescenceUnavailable,
338         machine.quiescenceReceipt(),
339     );
340     try std.testing.expectError(expected, machine.takeEvents(output));
341     try expectInstanceEqual(machine_before, machine.*);
342     try std.testing.expectEqualSlices(u8, &storage_before.bytes, &storage.bytes);
343     try std.testing.expectEqualSlices(
344         u8,
345         std.mem.asBytes(&requests_before),
346         std.mem.asBytes(layout.requestRing(machine.ram)),
347     );
348     try std.testing.expectEqualSlices(
349         u8,
350         std.mem.asBytes(&events_before),
351         std.mem.asBytes(layout.eventRing(machine.ram)),
352     );
353     try std.testing.expectEqual(phase_before, machine.phase());
354     try std.testing.expectEqualDeep(output_before, output.*);
355     try std.testing.expectError(
356         error.QuiescenceUnavailable,
357         machine.quiescenceReceipt(),
358     );
359 }
360 
361 fn expectInstanceEqual(
362     expected: instance.Instance,
363     actual: instance.Instance,
364 ) !void {
365     try std.testing.expectEqual(expected.storage, actual.storage);
366     try std.testing.expectEqual(expected.ram.ptr, actual.ram.ptr);
367     try std.testing.expectEqual(expected.ram.len, actual.ram.len);
368     try std.testing.expectEqual(
369         expected.session_identity,
370         actual.session_identity,
371     );
372 }
373 
374 fn outputSentinel() instance.EventBatch {
375     return .{
376         .count = 1,
377         .storage = @splat(@splat(0xa5)),
378     };
379 }
380 
381 fn realK0Input(
382     execution: os.boot.kernel.manifest.View,
383 ) instance.Input {
384     return .{
385         .profile = profile.interpretedContinuationTestV1(),
386         .elf = real_k0_elf,
387         .execution_manifest = real_k0_manifest,
388         .expected_execution_fingerprint = execution.header.fingerprint,
389         .fence = realK0Fence(),
390         .initial_time_tick = 7,
391         .entropy_generation = 1,
392         .terminal_offset = 0,
393         .effect_frontier = 0,
394         .block_root = @splat(0x33),
395         .source_root = @splat(0x77),
396         .input_frontier = 0,
397         .terminal_input_offset = 0,
398         .outstanding_effect = null,
399     };
400 }
401 
402 fn realK0Fence() os.abi.ActivationFence {
403     return .{
404         .world = @splat(0x44),
405         .generation = 1,
406         .token = @splat(0x55),
407     };
408 }
409 
410 comptime {
411     std.debug.assert(@sizeOf(instance.EventBatch) <= instance.storage_bytes);
412     std.debug.assert(@sizeOf(instance.EventBatch) <= layout.ram_bytes);
413 }