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 }