lib/machine/src/checkpoint/test.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const admission = @import("../admission/root.zig");
2 const checkpoint = @import("root.zig");
3 const checkpoint_owner = @import("owner/root.zig");
4 const core = @import("machine_instance_core");
5 const fixture = @import("../instance/fixture/root.zig").fixture;
6 const instance = @import("../instance/root.zig");
7 const os = @import("os");
8 const profile = @import("../profile/root.zig");
9 const std = @import("std");
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 test {
16 _ = @import("roots/test.zig");
17 }
18
19 var first_ram: [checkpoint.ram_bytes]u8 align(checkpoint.ram_alignment) = undefined;
20 var second_ram: [checkpoint.ram_bytes]u8 align(checkpoint.ram_alignment) = undefined;
21 var third_ram: [checkpoint.ram_bytes]u8 align(checkpoint.ram_alignment) = undefined;
22 var checkpoint_stream: [checkpoint.stream.stream_bytes + 1]u8 = undefined;
23
24 const Captured = struct {
25 value: checkpoint.Checkpoint,
26 receipt: instance.QuiescenceReceipt,
27 };
28
29 const Turn = struct {
30 delivery: admission.Delivery,
31 events: instance.EventBatch,
32 receipt: instance.QuiescenceReceipt,
33 };
34
35 const PageTableCorruption = struct {
36 address: u64,
37 index: usize,
38 mask: u64,
39 };
40
41 const FragmentedReader = struct {
42 source: []const u8,
43 chunk_bytes: usize,
44 offset: usize = 0,
45 failure_offset: ?usize = null,
46 claim_probe: ?*checkpoint.Storage = null,
47 claim_checked: bool = false,
48 claim_observed: bool = false,
49 claim_missing: bool = false,
50 buffer: [31]u8 = undefined,
51 reader: std.Io.Reader = .{
52 .vtable = &vtable,
53 .buffer = undefined,
54 .seek = 0,
55 .end = 0,
56 },
57
58 fn init(source: []const u8, chunk_bytes: usize) FragmentedReader {
59 std.debug.assert(chunk_bytes > 0);
60 return .{ .source = source, .chunk_bytes = chunk_bytes };
61 }
62
63 fn attachUnbuffered(self: *FragmentedReader) void {
64 self.reader.buffer = self.buffer[0..0];
65 }
66
67 const vtable: std.Io.Reader.VTable = .{
68 .stream = stream,
69 .discard = discard,
70 .readVec = readVec,
71 };
72
73 fn stream(
74 reader: *std.Io.Reader,
75 writer: *std.Io.Writer,
76 limit: std.Io.Limit,
77 ) std.Io.Reader.StreamError!usize {
78 const self: *FragmentedReader = @alignCast(
79 @fieldParentPtr("reader", reader),
80 );
81 var scratch: [4096]u8 = undefined;
82 const destination = limit.slice(&scratch);
83 if (destination.len == 0) return 0;
84 const length = try self.readInto(destination);
85 if (length == 0) return error.EndOfStream;
86 writer.writeAll(destination[0..length]) catch
87 return error.WriteFailed;
88 return length;
89 }
90
91 fn discard(
92 reader: *std.Io.Reader,
93 limit: std.Io.Limit,
94 ) std.Io.Reader.Error!usize {
95 const self: *FragmentedReader = @alignCast(
96 @fieldParentPtr("reader", reader),
97 );
98 const destination = limit.slice(reader.buffer);
99 if (destination.len == 0) return 0;
100 const length = try self.readInto(destination);
101 if (length == 0) return error.EndOfStream;
102 return length;
103 }
104
105 fn readVec(
106 reader: *std.Io.Reader,
107 data: [][]u8,
108 ) std.Io.Reader.Error!usize {
109 const self: *FragmentedReader = @alignCast(
110 @fieldParentPtr("reader", reader),
111 );
112 for (data) |destination| {
113 if (destination.len == 0) continue;
114 const length = try self.readInto(destination);
115 if (length == 0) return error.EndOfStream;
116 return length;
117 }
118 const destination = reader.buffer[reader.end..];
119 if (destination.len == 0) return 0;
120 const length = try self.readInto(destination);
121 if (length == 0) return error.EndOfStream;
122 reader.end += length;
123 return 0;
124 }
125
126 fn readInto(
127 self: *FragmentedReader,
128 destination: []u8,
129 ) std.Io.Reader.Error!usize {
130 if (!self.claim_checked and
131 self.offset >= checkpoint.stream.header_bytes)
132 {
133 if (self.claim_probe) |storage| {
134 self.claim_checked = true;
135 if (checkpoint_owner.ensureAvailable(storage)) |_| {
136 self.claim_missing = true;
137 } else |failure| switch (failure) {
138 error.CheckpointStorageInUse => {
139 self.claim_observed = true;
140 },
141 else => return error.ReadFailed,
142 }
143 }
144 }
145 if (self.failure_offset) |offset| {
146 if (self.offset >= offset) return error.ReadFailed;
147 }
148 const remaining = self.source.len - self.offset;
149 const length = @min(destination.len, self.chunk_bytes, remaining);
150 @memcpy(destination[0..length], self.source[self.offset..][0..length]);
151 self.offset += length;
152 return length;
153 }
154 };
155
156 const ComparingWriter = struct {
157 expected: []const u8,
158 chunk_bytes: usize,
159 offset: usize = 0,
160 mismatch: bool = false,
161 buffer: [0]u8 = .{},
162 writer: std.Io.Writer = .{
163 .vtable = &vtable,
164 .buffer = undefined,
165 },
166
167 fn init(expected: []const u8, chunk_bytes: usize) ComparingWriter {
168 std.debug.assert(chunk_bytes > 0);
169 return .{ .expected = expected, .chunk_bytes = chunk_bytes };
170 }
171
172 fn attach(self: *ComparingWriter) void {
173 self.writer.buffer = &self.buffer;
174 }
175
176 const vtable: std.Io.Writer.VTable = .{ .drain = drain };
177
178 fn drain(
179 writer: *std.Io.Writer,
180 data: []const []const u8,
181 splat: usize,
182 ) std.Io.Writer.Error!usize {
183 const self: *ComparingWriter = @alignCast(
184 @fieldParentPtr("writer", writer),
185 );
186 std.debug.assert(writer.end == 0);
187 var budget = self.chunk_bytes;
188 var consumed: usize = 0;
189 for (data[0 .. data.len - 1]) |bytes| {
190 const length = self.compare(bytes, budget);
191 consumed += length;
192 budget -= length;
193 if (length != bytes.len or budget == 0) return consumed;
194 }
195 const pattern = data[data.len - 1];
196 var repetition: usize = 0;
197 while (repetition < splat and budget > 0) : (repetition += 1) {
198 const length = self.compare(pattern, budget);
199 consumed += length;
200 budget -= length;
201 if (length != pattern.len) return consumed;
202 }
203 return consumed;
204 }
205
206 fn compare(self: *ComparingWriter, bytes: []const u8, limit: usize) usize {
207 const length = @min(bytes.len, limit);
208 const available = if (self.offset < self.expected.len)
209 self.expected.len - self.offset
210 else
211 0;
212 const compared = @min(length, available);
213 if (!std.mem.eql(
214 u8,
215 bytes[0..compared],
216 self.expected[self.offset..][0..compared],
217 ) or compared != length) {
218 self.mismatch = true;
219 }
220 self.offset += length;
221 return length;
222 }
223 };
224
225 test "canonical checkpoint root is neutral to activation fences" {
226 var first_storage = checkpoint.Storage.init();
227 const first = try captureAt(
228 7,
229 0x71,
230 &first_storage,
231 &first_ram,
232 );
233 try first.value.verify();
234 const first_root = try first.value.root();
235
236 var second_storage = checkpoint.Storage.init();
237 const second = try captureAt(
238 11,
239 0x91,
240 &second_storage,
241 &second_ram,
242 );
243 try second.value.verify();
244 const second_root = try second.value.root();
245
246 try std.testing.expect(!std.mem.eql(
247 u8,
248 &first.receipt.fenced_digest,
249 &second.receipt.fenced_digest,
250 ));
251 try std.testing.expectEqualDeep(first_root.state, second_root.state);
252 try std.testing.expectEqualDeep(first_root, second_root);
253 try std.testing.expectEqualSlices(u8, &first_ram, &second_ram);
254
255 const reopened = try checkpoint.open(&first_storage, &first_ram, first_root);
256 try std.testing.expectEqualDeep(first_root, try reopened.root());
257 var wrong_root = first_root;
258 wrong_root.digest[0] ^= 1;
259 try std.testing.expectError(
260 error.CheckpointRootMismatch,
261 checkpoint.open(&first_storage, &first_ram, wrong_root),
262 );
263 try std.testing.expectError(
264 error.CheckpointRootMismatch,
265 checkpoint.recoverAfterCrash(&first_storage, &first_ram, null),
266 );
267 @memcpy(&third_ram, &first_ram);
268 const relocated = try checkpoint.open(&first_storage, &third_ram, first_root);
269 try std.testing.expectEqualDeep(first_root, try relocated.root());
270 switch (try checkpoint.recoverAfterCrash(
271 &first_storage,
272 &first_ram,
273 first_root,
274 )) {
275 .published => |recovered| {
276 try std.testing.expectEqualDeep(first_root, try recovered.root());
277 },
278 .empty => return error.TestExpectedPublishedCheckpoint,
279 }
280 }
281
282 test "canonical checkpoint stream is fence-neutral and round trips exactly" {
283 var first_storage = checkpoint.Storage.init();
284 const first = try captureAt(7, 0x71, &first_storage, &first_ram);
285 const expected_root = try first.value.root();
286 try encodeCheckpointStream(&first.value);
287 const first_digest = streamDigest();
288 try std.testing.expectEqualSlices(u8, &[_]u8{
289 'M', 'C', 'H', 'C', 'K', 'P', '1', 0,
290 1, 0, 0, 16, 0, 0, 1, 0,
291 0, 16, 0, 4, 0, 0, 0, 0,
292 0, 0, 0, 4, 0, 0, 0, 0,
293 0, 16, 0, 0, 1, 0, 2, 0,
294 4, 0, 32, 0, 32, 0, 96, 0,
295 56, 1, 72, 0, 168, 0,
296 }, checkpoint_stream[0..54]);
297 try expectZero(checkpoint_stream[54..64]);
298
299 var second_storage = checkpoint.Storage.init();
300 const second = try captureAt(11, 0x91, &second_storage, &second_ram);
301 try std.testing.expectEqualDeep(expected_root, try second.value.root());
302 try expectExactCheckpointStream(&second.value);
303
304 var reopened_storage = checkpoint.Storage.init();
305 var source_reader = FragmentedReader.init(
306 checkpoint_stream[0..checkpoint.stream.stream_bytes],
307 4093,
308 );
309 source_reader.claim_probe = &reopened_storage;
310 source_reader.attachUnbuffered();
311 const reopened = try checkpoint.stream.decodeDisjoint(
312 &source_reader.reader,
313 expected_root,
314 &reopened_storage,
315 &third_ram,
316 );
317 try std.testing.expect(source_reader.claim_checked);
318 try std.testing.expect(source_reader.claim_observed);
319 try std.testing.expect(!source_reader.claim_missing);
320 try reopened.verify();
321 try std.testing.expectEqualDeep(expected_root, try reopened.root());
322 try expectExactCheckpointStream(&reopened);
323 try std.testing.expectEqualSlices(u8, &first_digest, &streamDigest());
324 }
325
326 test "checkpoint stream rejects bad framing corruption and aliases unpublished" {
327 const execution = try os.boot.kernel.manifest.parse(real_k0_manifest);
328 var source_storage = checkpoint.Storage.init();
329 const source = try captureAt(47, 0xb1, &source_storage, &first_ram);
330 const expected_root = try source.value.root();
331
332 var short_writer = std.Io.Writer.fixed(
333 checkpoint_stream[0 .. checkpoint.stream.stream_bytes - 1],
334 );
335 try std.testing.expectError(
336 error.WriteFailed,
337 checkpoint.stream.encodeDisjoint(&source.value, &short_writer),
338 );
339 try encodeCheckpointStream(&source.value);
340
341 var destination_storage = checkpoint.Storage.init();
342 try expectStreamDecodeFailure(
343 error.TruncatedStream,
344 checkpoint_stream[0 .. checkpoint.stream.header_bytes - 1],
345 expected_root,
346 &destination_storage,
347 &second_ram,
348 );
349 try expectStreamDecodeFailure(
350 error.TruncatedStream,
351 checkpoint_stream[0 .. checkpoint.stream.stream_bytes - 1],
352 expected_root,
353 &destination_storage,
354 &second_ram,
355 );
356 try expectStreamReadFailure(
357 checkpoint.stream.header_bytes / 2,
358 expected_root,
359 &destination_storage,
360 &second_ram,
361 );
362 try expectStreamReadFailure(
363 checkpoint.stream.header_bytes + 8192,
364 expected_root,
365 &destination_storage,
366 &second_ram,
367 );
368 try expectStreamReadFailure(
369 checkpoint.stream.stream_bytes,
370 expected_root,
371 &destination_storage,
372 &second_ram,
373 );
374
375 checkpoint_stream[checkpoint.stream.stream_bytes] = 0xa5;
376 try expectStreamDecodeFailure(
377 error.TrailingData,
378 &checkpoint_stream,
379 expected_root,
380 &destination_storage,
381 &second_ram,
382 );
383
384 const header_mutations = [_]struct {
385 offset: usize,
386 expected: anyerror,
387 }{
388 .{ .offset = 0, .expected = error.BadMagic },
389 .{ .offset = 8, .expected = error.UnsupportedVersion },
390 .{ .offset = 10, .expected = error.HeaderBytesMismatch },
391 .{ .offset = 12, .expected = error.UnsupportedFlags },
392 .{ .offset = 14, .expected = error.CheckpointFormatMismatch },
393 .{ .offset = 16, .expected = error.StreamBytesMismatch },
394 .{ .offset = 24, .expected = error.StreamBytesMismatch },
395 .{ .offset = 32, .expected = error.PageBytesMismatch },
396 .{ .offset = 36, .expected = error.AbiVersionMismatch },
397 .{ .offset = 38, .expected = error.AbiVersionMismatch },
398 .{ .offset = 40, .expected = error.MetadataShapeMismatch },
399 .{ .offset = 54, .expected = error.ReservedNonzero },
400 .{ .offset = 465, .expected = error.ReservedNonzero },
401 .{ .offset = 572, .expected = error.ReservedNonzero },
402 .{ .offset = 644, .expected = error.ReservedNonzero },
403 .{ .offset = 676, .expected = error.ReservedNonzero },
404 .{ .offset = 708, .expected = error.ReservedNonzero },
405 .{ .offset = 740, .expected = error.ReservedNonzero },
406 .{ .offset = 744, .expected = error.ReservedNonzero },
407 };
408 for (header_mutations) |mutation| {
409 checkpoint_stream[mutation.offset] ^= 1;
410 try expectStreamDecodeFailure(
411 mutation.expected,
412 checkpoint_stream[0..checkpoint.stream.stream_bytes],
413 expected_root,
414 &destination_storage,
415 &second_ram,
416 );
417 checkpoint_stream[mutation.offset] ^= 1;
418 }
419
420 var wrong_expected = expected_root;
421 wrong_expected.digest[0] ^= 1;
422 try expectStreamDecodeFailure(
423 error.CheckpointRootMismatch,
424 checkpoint_stream[0..checkpoint.stream.stream_bytes],
425 wrong_expected,
426 &destination_storage,
427 &second_ram,
428 );
429
430 checkpoint_stream[128] ^= 1;
431 try expectStreamDecodeFailure(
432 error.CheckpointRootMismatch,
433 checkpoint_stream[0..checkpoint.stream.stream_bytes],
434 expected_root,
435 &destination_storage,
436 &second_ram,
437 );
438 checkpoint_stream[128] ^= 1;
439
440 checkpoint_stream[192] ^= 1;
441 try expectStreamDecodeFailure(
442 error.CheckpointRootMismatch,
443 checkpoint_stream[0..checkpoint.stream.stream_bytes],
444 expected_root,
445 &destination_storage,
446 &second_ram,
447 );
448 checkpoint_stream[192] ^= 1;
449
450 const mutable = try firstWritableAddress(execution);
451 checkpoint_stream[checkpoint.stream.header_bytes + mutable] ^= 1;
452 try expectStreamDecodeFailure(
453 error.MemoryDigestMismatch,
454 checkpoint_stream[0..checkpoint.stream.stream_bytes],
455 expected_root,
456 &destination_storage,
457 &second_ram,
458 );
459 checkpoint_stream[checkpoint.stream.header_bytes + mutable] ^= 1;
460
461 var valid_reader = std.Io.Reader.fixed(
462 checkpoint_stream[0..checkpoint.stream.stream_bytes],
463 );
464 const reopened = try checkpoint.stream.decodeDisjoint(
465 &valid_reader,
466 expected_root,
467 &destination_storage,
468 &second_ram,
469 );
470 try reopened.verify();
471
472 var aliased_reader = std.Io.Reader.fixed(&third_ram);
473 var alias_storage = checkpoint.Storage.init();
474 try std.testing.expectError(
475 error.SourceAliasesDestination,
476 checkpoint.stream.decodeDisjoint(
477 &aliased_reader,
478 expected_root,
479 &alias_storage,
480 &third_ram,
481 ),
482 );
483 var aliased_writer = std.Io.Writer.fixed(&first_ram);
484 try std.testing.expectError(
485 error.SinkAliasesCheckpoint,
486 checkpoint.stream.encodeDisjoint(&source.value, &aliased_writer),
487 );
488 }
489
490 test "every publication crash cut exposes only empty or authenticated state" {
491 var source_storage = checkpoint.Storage.init();
492 const source = try captureAt(12, 0x81, &source_storage, &first_ram);
493 const expected = try source.value.root();
494
495 inline for (std.meta.tags(checkpoint_owner.publication_audit.Cut)) |cut| {
496 var storage = checkpoint.Storage.init();
497 const outcome = try checkpoint_owner.publication_audit.replay(
498 &source.value,
499 &storage,
500 &second_ram,
501 cut,
502 );
503 if (cut == .published) {
504 const published = switch (outcome) {
505 .published => |value| value,
506 .interrupted => return error.TestExpectedPublishedCheckpoint,
507 };
508 try std.testing.expectEqualDeep(expected, try published.root());
509 switch (try checkpoint.recoverAfterCrash(
510 &storage,
511 &second_ram,
512 expected,
513 )) {
514 .published => |recovered| {
515 try std.testing.expectEqualDeep(
516 expected,
517 try recovered.root(),
518 );
519 },
520 .empty => return error.TestExpectedPublishedCheckpoint,
521 }
522 } else {
523 switch (outcome) {
524 .interrupted => {},
525 .published => return error.TestExpectedInterruptedCheckpoint,
526 }
527 try std.testing.expectError(
528 error.CheckpointClosed,
529 checkpoint.open(&storage, &second_ram, expected),
530 );
531 switch (try checkpoint.recoverAfterCrash(
532 &storage,
533 &second_ram,
534 null,
535 )) {
536 .empty => {},
537 .published => return error.TestExpectedEmptyCheckpoint,
538 }
539 try std.testing.expectError(
540 error.CheckpointClosed,
541 checkpoint.open(&storage, &second_ram, expected),
542 );
543 }
544 }
545 }
546
547 test "checkpoint normalization is exact and corruption fails closed" {
548 var storage = checkpoint.Storage.init();
549 const captured = try captureAt(13, 0xa1, &storage, &first_ram);
550 const regions = [_]struct { address: usize, bytes: usize }{
551 .{ .address = @intCast(layout.boot_frame_address), .bytes = layout.page_bytes },
552 .{ .address = @intCast(layout.request_ring_address), .bytes = layout.page_bytes },
553 .{ .address = @intCast(layout.event_ring_address), .bytes = layout.page_bytes },
554 .{
555 .address = @intCast(layout.stack_base),
556 .bytes = @intCast(layout.stack_end - layout.stack_base),
557 },
558 };
559 for (regions) |region| {
560 try expectZero(first_ram[region.address..][0..region.bytes]);
561 first_ram[region.address + region.bytes - 1] = 1;
562 try std.testing.expectError(
563 error.MemoryNotNormalized,
564 captured.value.verify(),
565 );
566 first_ram[region.address + region.bytes - 1] = 0;
567 try captured.value.verify();
568 }
569
570 const preserved: usize = @intCast(layout.event_ring_address + layout.page_bytes);
571 first_ram[preserved] ^= 1;
572 try std.testing.expectError(
573 error.MemoryDigestMismatch,
574 captured.value.verify(),
575 );
576 first_ram[preserved] ^= 1;
577 try captured.value.verify();
578 }
579
580 test "immutable checkpoint storage cannot be overwritten" {
581 var storage = checkpoint.Storage.init();
582 const captured = try captureAt(17, 0xb1, &storage, &first_ram);
583 try captured.value.verify();
584
585 const execution = try os.boot.kernel.manifest.parse(real_k0_manifest);
586 const selected = input(execution, 19, 0xb2);
587 var live_storage = instance.Storage.init();
588 var machine = try start(&live_storage, selected);
589 defer machine.deinit();
590 _ = try driveToReceipt(&machine, selected.fence);
591 try std.testing.expectError(
592 error.CheckpointStorageInUse,
593 machine.captureCheckpoint(&storage, &second_ram),
594 );
595 try std.testing.expectError(
596 error.CheckpointStorageInUse,
597 machine.captureCheckpoint(&storage, &first_ram),
598 );
599 try captured.value.verify();
600 }
601
602 test "checkpoint capture rejects every changed launch mapping" {
603 const execution = try os.boot.kernel.manifest.parse(real_k0_manifest);
604 const selected = input(execution, 31, 0xe1);
605 var live_storage = instance.Storage.init();
606 var machine = try start(&live_storage, selected);
607 defer machine.deinit();
608 _ = try driveToReceipt(&machine, selected.fence);
609 markTranslationActivity(machine.ram);
610
611 var accepted_storage = checkpoint.Storage.init();
612 const accepted = try machine.captureCheckpoint(&accepted_storage, &first_ram);
613 try accepted.verify();
614
615 const protection_plan = try core.protection.Plan.init(execution);
616 const executable = protection_plan.sealedSpans()[0].ram_offset;
617 const executable_table = (executable - os.boot.kernel.physical_base) /
618 layout.large_page_bytes;
619 const executable_index = executable % layout.large_page_bytes / layout.page_bytes;
620 const corruptions = [_]PageTableCorruption{
621 .{ .address = layout.pml4_address, .index = 0, .mask = 1 << 1 },
622 .{ .address = layout.pml4_address, .index = 1, .mask = 1 },
623 .{ .address = layout.pdpt_address, .index = 0, .mask = 1 << 12 },
624 .{ .address = layout.page_directory_address, .index = 7, .mask = 1 << 7 },
625 .{ .address = layout.page_directory_address, .index = 32, .mask = 1 },
626 .{ .address = layout.low_page_table_address, .index = 1, .mask = 1 },
627 .{
628 .address = layout.kernel_page_table_base +
629 executable_table * layout.page_bytes,
630 .index = @intCast(executable_index),
631 .mask = 1 << 1,
632 },
633 .{
634 .address = layout.kernel_page_table_base +
635 executable_table * layout.page_bytes,
636 .index = @intCast(executable_index),
637 .mask = 1 << 6,
638 },
639 };
640 var rejected_storage = checkpoint.Storage.init();
641 for (corruptions) |corruption| {
642 toggleEntryBits(
643 machine.ram,
644 corruption.address,
645 corruption.index,
646 corruption.mask,
647 );
648 try std.testing.expectError(
649 error.MemoryNotNormalized,
650 machine.captureCheckpoint(&rejected_storage, &second_ram),
651 );
652 switch (try checkpoint.recoverAfterCrash(
653 &rejected_storage,
654 &second_ram,
655 null,
656 )) {
657 .empty => {},
658 .published => return error.TestExpectedEmptyCheckpoint,
659 }
660 toggleEntryBits(
661 machine.ram,
662 corruption.address,
663 corruption.index,
664 corruption.mask,
665 );
666 }
667 }
668
669 test "checkpoint capture binds every immutable load and preserves writable state" {
670 const execution = try os.boot.kernel.manifest.parse(real_k0_manifest);
671 const selected = input(execution, 37, 0xf1);
672 var live_storage = instance.Storage.init();
673 var machine = try start(&live_storage, selected);
674 defer machine.deinit();
675 _ = try driveToReceipt(&machine, selected.fence);
676
677 var rejected_storage = checkpoint.Storage.init();
678 var immutable_count: u16 = 0;
679 var writable_address: ?usize = null;
680 var index: u16 = 0;
681 while (index < execution.header.load_count) : (index += 1) {
682 const load = execution.load(index);
683 const address: usize = @intCast(
684 execution.header.facts.physical_base + load.physical_offset,
685 );
686 if (load.flags & os.boot.kernel.manifest.load_flag_write != 0) {
687 if (writable_address == null) writable_address = address;
688 continue;
689 }
690 immutable_count += 1;
691 machine.ram[address] ^= 1;
692 try std.testing.expectError(
693 error.ImmutableImageMismatch,
694 machine.captureCheckpoint(&rejected_storage, &first_ram),
695 );
696 switch (try checkpoint.recoverAfterCrash(
697 &rejected_storage,
698 &first_ram,
699 null,
700 )) {
701 .empty => {},
702 .published => return error.TestExpectedEmptyCheckpoint,
703 }
704 machine.ram[address] ^= 1;
705 }
706 try std.testing.expect(immutable_count > 0);
707
708 const mutable = writable_address orelse return error.TestExpectedWritableLoad;
709 machine.ram[mutable] ^= 1;
710 var accepted_storage = checkpoint.Storage.init();
711 const accepted = try machine.captureCheckpoint(&accepted_storage, &second_ram);
712 try accepted.verify();
713 }
714
715 test "checkpoint root corruption is distinguished from a closed handle" {
716 var storage = checkpoint.Storage.init();
717 const captured = try captureAt(41, 0xa3, &storage, &first_ram);
718 const root_value = try captured.value.root();
719 const offset = std.mem.indexOf(u8, &storage.bytes, &root_value.digest) orelse
720 return error.TestExpectedStoredRoot;
721 storage.bytes[offset] ^= 1;
722 try std.testing.expectError(
723 error.CheckpointCorrupt,
724 captured.value.verify(),
725 );
726 storage.bytes[offset] ^= 1;
727 try captured.value.verify();
728 }
729
730 test "adjacent checkpoints preserve the prior root and advance logical state" {
731 const execution = try os.boot.kernel.manifest.parse(real_k0_manifest);
732 const selected = input(execution, 23, 0xc1);
733 var live_storage = instance.Storage.init();
734 var machine = try start(&live_storage, selected);
735 defer machine.deinit();
736
737 _ = try driveTerminalToReceipt(&machine, selected.fence, 0);
738 var first_storage = checkpoint.Storage.init();
739 const first = try machine.captureCheckpoint(&first_storage, &first_ram);
740 const first_root = try first.root();
741
742 var next = selected.fence;
743 next.generation += 1;
744 next.token = @splat(0xc2);
745 try machine.reactivate(next);
746 _ = try driveTerminalToReceipt(
747 &machine,
748 next,
749 os.k0.request_bytes.len,
750 );
751 var second_storage = checkpoint.Storage.init();
752 const second = try machine.captureCheckpoint(&second_storage, &second_ram);
753 const second_root = try second.root();
754
755 try std.testing.expect(!std.meta.eql(first_root, second_root));
756 try std.testing.expect(!std.meta.eql(first_root.state, second_root.state));
757 try std.testing.expectEqualSlices(u8, &first_ram, &second_ram);
758 try std.testing.expectEqualDeep(first_root, try first.root());
759 }
760
761 test "one authenticated root restores twice under distinct supplied authority" {
762 const execution = try os.boot.kernel.manifest.parse(real_k0_manifest);
763 const selected = input(execution, 53, 0xa5);
764 var source_storage = instance.Storage.init();
765 var source = try start(&source_storage, selected);
766 _ = try activate(&source);
767 const source_basis = try source.admissionBasis();
768 const source_admission = try admission.prepare(
769 source_basis,
770 try admission.terminal(
771 source_basis.frontiers.terminal_input_offset,
772 os.k0.request_bytes,
773 ),
774 );
775 const source_turn = try completeTurn(
776 &source,
777 source_admission,
778 @splat(0x81),
779 selected.fence,
780 );
781 var source_checkpoint_storage = checkpoint.Storage.init();
782 const source_checkpoint = try source.captureCheckpoint(
783 &source_checkpoint_storage,
784 &first_ram,
785 );
786 const source_root = try source_checkpoint.root();
787 const source_session = source.session_identity;
788 source.deinit();
789
790 const first_fence: os.abi.ActivationFence = .{
791 .world = @splat(0xb1),
792 .generation = 1,
793 .token = @splat(0xb2),
794 };
795 const second_fence: os.abi.ActivationFence = .{
796 .world = @splat(0xc1),
797 .generation = 1,
798 .token = @splat(0xc2),
799 };
800 var first_storage = instance.Storage.init();
801 var first_branch = try restore(
802 &first_storage,
803 &second_ram,
804 source_checkpoint,
805 source_root,
806 first_fence,
807 );
808 defer first_branch.deinit();
809 var second_storage = instance.Storage.init();
810 var second_branch = try restore(
811 &second_storage,
812 &third_ram,
813 source_checkpoint,
814 source_root,
815 second_fence,
816 );
817 defer second_branch.deinit();
818
819 try std.testing.expect(first_branch.session_identity != source_session);
820 try std.testing.expect(second_branch.session_identity != source_session);
821 try std.testing.expect(
822 first_branch.session_identity != second_branch.session_identity,
823 );
824 try expectRestartFrame(&first_branch, source_turn.receipt, first_fence);
825 try expectRestartFrame(&second_branch, source_turn.receipt, second_fence);
826
827 const first_activation = try activate(&first_branch);
828 const second_activation = try activate(&second_branch);
829 const comparison_fence: os.abi.ActivationFence = .{
830 .world = @splat(0xd1),
831 .generation = 7,
832 .token = @splat(0xd2),
833 };
834 try expectEquivalentEvents(
835 first_activation,
836 second_activation,
837 comparison_fence,
838 );
839 try std.testing.expectError(
840 error.DeliveryFenceMismatch,
841 first_branch.deliverAdmitted(&source_turn.delivery),
842 );
843 try std.testing.expectError(
844 error.DeliveryFenceMismatch,
845 second_branch.deliverAdmitted(&source_turn.delivery),
846 );
847 try std.testing.expectError(
848 error.InvalidQuiescenceReceipt,
849 instance.verifyQuiescenceReceipt(source_turn.receipt, first_fence),
850 );
851 try std.testing.expectError(
852 error.InvalidQuiescenceReceipt,
853 instance.verifyQuiescenceReceipt(source_turn.receipt, second_fence),
854 );
855 try std.testing.expectError(
856 error.StaleActivation,
857 os.abi.EventRing.push(
858 layout.eventRing(first_branch.ram),
859 first_fence,
860 &source_turn.events.storage[0],
861 ),
862 );
863
864 const first_basis = try first_branch.admissionBasis();
865 const second_basis = try second_branch.admissionBasis();
866 try std.testing.expectEqualDeep(first_basis, second_basis);
867 const shared_admission = try admission.prepare(
868 first_basis,
869 try admission.terminal(
870 first_basis.frontiers.terminal_input_offset,
871 os.k0.request_bytes,
872 ),
873 );
874 const cross_delivery = try admission.bindDelivery(
875 shared_admission,
876 @splat(0x91),
877 second_fence,
878 );
879 try std.testing.expectError(
880 error.DeliveryFenceMismatch,
881 first_branch.deliverAdmitted(&cross_delivery),
882 );
883 const first_turn = try completeTurn(
884 &first_branch,
885 shared_admission,
886 @splat(0x91),
887 first_fence,
888 );
889 const second_turn = try completeTurn(
890 &second_branch,
891 shared_admission,
892 @splat(0x91),
893 second_fence,
894 );
895 try expectEquivalentEvents(
896 first_turn.events,
897 second_turn.events,
898 comparison_fence,
899 );
900 try std.testing.expect(!std.mem.eql(
901 u8,
902 &first_turn.receipt.fenced_digest,
903 &second_turn.receipt.fenced_digest,
904 ));
905 try std.testing.expectEqualDeep(
906 first_turn.receipt.basis,
907 second_turn.receipt.basis,
908 );
909 try std.testing.expectEqualDeep(
910 first_turn.receipt.block_root,
911 second_turn.receipt.block_root,
912 );
913 try std.testing.expectEqual(
914 first_fence.generation,
915 first_turn.receipt.boundary.capability_generation,
916 );
917 try std.testing.expectEqual(
918 second_fence.generation,
919 second_turn.receipt.boundary.capability_generation,
920 );
921
922 var equal_checkpoint_storage = checkpoint.Storage.init();
923 const equal_checkpoint = try first_branch.captureCheckpoint(
924 &equal_checkpoint_storage,
925 &fixture.ram,
926 );
927 const equal_root = try equal_checkpoint.root();
928 const second_storage_before = storageDigest(&second_storage);
929 const second_ram_before = ramDigest(second_branch.ram);
930 const second_receipt_before = try second_branch.quiescenceReceipt();
931
932 var next_first_fence = first_fence;
933 next_first_fence.generation += 1;
934 next_first_fence.token = @splat(0xb3);
935 try first_branch.reactivate(next_first_fence);
936 _ = try activate(&first_branch);
937 const divergent_basis = try first_branch.admissionBasis();
938 const divergent_admission = try admission.prepare(
939 divergent_basis,
940 try admission.virtualTime(
941 divergent_basis.frontiers.virtual_time_tick,
942 divergent_basis.frontiers.virtual_time_tick + 1,
943 ),
944 );
945 const divergent = try completeTurn(
946 &first_branch,
947 divergent_admission,
948 @splat(0xa1),
949 next_first_fence,
950 );
951 try std.testing.expectEqual(
952 second_turn.receipt.block_root.generation + 1,
953 divergent.receipt.block_root.generation,
954 );
955 try std.testing.expectEqual(
956 second_turn.receipt.basis.frontiers.virtual_time_tick + 1,
957 divergent.receipt.basis.frontiers.virtual_time_tick,
958 );
959 const second_storage_after = storageDigest(&second_storage);
960 const second_ram_after = ramDigest(second_branch.ram);
961 try std.testing.expectEqualSlices(
962 u8,
963 &second_storage_before,
964 &second_storage_after,
965 );
966 try std.testing.expectEqualSlices(u8, &second_ram_before, &second_ram_after);
967 try std.testing.expectEqualDeep(
968 second_receipt_before,
969 try second_branch.quiescenceReceipt(),
970 );
971 try source_checkpoint.verify();
972
973 first_branch.deinit();
974 var second_checkpoint_storage = checkpoint.Storage.init();
975 const second_checkpoint = try second_branch.captureCheckpoint(
976 &second_checkpoint_storage,
977 &second_ram,
978 );
979 try std.testing.expectEqualDeep(equal_root, try second_checkpoint.root());
980 try equal_checkpoint.verify();
981 try std.testing.expect(!std.meta.eql(source_root, equal_root));
982 }
983
984 test "authenticated settled prefix restores into branch-private divergent states" {
985 const execution = try os.boot.kernel.manifest.parse(real_k0_manifest);
986 const effect_request: admission.EffectRequest = .{
987 .receipt = .{ .digest = @splat(0x71) },
988 .correlation = 1,
989 };
990 var selected = input(execution, 59, 0xd1);
991 selected.outstanding_effect = effect_request;
992 var source_storage = instance.Storage.init();
993 var source = try start(&source_storage, selected);
994 var source_closed = false;
995 defer if (!source_closed) source.deinit();
996 _ = try activate(&source);
997 const source_basis = try source.admissionBasis();
998 try std.testing.expectEqualDeep(
999 effect_request,
1000 source_basis.outstanding_effect.?,
1001 );
1002 const settled_record = try admission.effectResult(
1003 effect_request.receipt.digest,
1004 effect_request.correlation,
1005 .ok,
1006 @splat(0x72),
1007 "settled-prefix",
1008 );
1009 const settled_admission = try admission.prepare(
1010 source_basis,
1011 settled_record,
1012 );
1013 const source_turn = try completeTurn(
1014 &source,
1015 settled_admission,
1016 @splat(0x73),
1017 selected.fence,
1018 );
1019 try std.testing.expectEqual(
1020 @as(u64, 1),
1021 source_turn.receipt.basis.frontiers.effect,
1022 );
1023 try std.testing.expectEqual(
1024 @as(u64, 1),
1025 source_turn.receipt.boundary.effect_frontier,
1026 );
1027 try std.testing.expect(source_turn.receipt.basis.outstanding_effect == null);
1028
1029 var source_checkpoint_storage = checkpoint.Storage.init();
1030 const source_checkpoint = try source.captureCheckpoint(
1031 &source_checkpoint_storage,
1032 &first_ram,
1033 );
1034 const source_root = try source_checkpoint.root();
1035 try encodeCheckpointStream(&source_checkpoint);
1036 source.deinit();
1037 source_closed = true;
1038 source_storage = instance.Storage.init();
1039 source_checkpoint_storage = checkpoint.Storage.init();
1040 @memset(&fixture.ram, 0x5a);
1041 @memset(&first_ram, 0xa5);
1042 try std.testing.expect(!source_checkpoint.active());
1043
1044 var reopened_storage = checkpoint.Storage.init();
1045 var source_reader = std.Io.Reader.fixed(
1046 checkpoint_stream[0..checkpoint.stream.stream_bytes],
1047 );
1048 const reopened_checkpoint = try checkpoint.stream.decodeDisjoint(
1049 &source_reader,
1050 source_root,
1051 &reopened_storage,
1052 &second_ram,
1053 );
1054 try std.testing.expectEqualDeep(source_root, try reopened_checkpoint.root());
1055 const reopened_storage_before = ramDigest(&reopened_storage.bytes);
1056 const reopened_ram_before = ramDigest(&second_ram);
1057
1058 const first_fence: os.abi.ActivationFence = .{
1059 .world = @splat(0x81),
1060 .generation = 1,
1061 .token = @splat(0x82),
1062 };
1063 const second_fence: os.abi.ActivationFence = .{
1064 .world = @splat(0x91),
1065 .generation = 1,
1066 .token = @splat(0x92),
1067 };
1068 var first_storage = instance.Storage.init();
1069 var first_branch = try restore(
1070 &first_storage,
1071 &first_ram,
1072 reopened_checkpoint,
1073 source_root,
1074 first_fence,
1075 );
1076 var first_closed = false;
1077 defer if (!first_closed) first_branch.deinit();
1078 var second_storage = instance.Storage.init();
1079 var second_branch = try restore(
1080 &second_storage,
1081 &third_ram,
1082 reopened_checkpoint,
1083 source_root,
1084 second_fence,
1085 );
1086 var second_closed = false;
1087 defer if (!second_closed) second_branch.deinit();
1088
1089 try expectRestartFrame(&first_branch, source_turn.receipt, first_fence);
1090 try expectRestartFrame(&second_branch, source_turn.receipt, second_fence);
1091 const first_activation = try activate(&first_branch);
1092 const second_activation = try activate(&second_branch);
1093 try expectEquivalentEvents(first_activation, second_activation, first_fence);
1094 const first_basis = try first_branch.admissionBasis();
1095 const second_basis = try second_branch.admissionBasis();
1096 try std.testing.expectEqualDeep(source_turn.receipt.basis, first_basis);
1097 try std.testing.expectEqualDeep(first_basis, second_basis);
1098
1099 const second_storage_before_first = storageDigest(&second_storage);
1100 const second_ram_before_first = ramDigest(second_branch.ram);
1101 const first_admission = try admission.prepare(
1102 first_basis,
1103 try admission.virtualTime(
1104 first_basis.frontiers.virtual_time_tick,
1105 first_basis.frontiers.virtual_time_tick + 1,
1106 ),
1107 );
1108 const first_turn = try completeTurn(
1109 &first_branch,
1110 first_admission,
1111 @splat(0xa1),
1112 first_fence,
1113 );
1114 try std.testing.expectEqualSlices(
1115 u8,
1116 &second_storage_before_first,
1117 &storageDigest(&second_storage),
1118 );
1119 try std.testing.expectEqualSlices(
1120 u8,
1121 &second_ram_before_first,
1122 &ramDigest(second_branch.ram),
1123 );
1124
1125 const first_storage_before_second = storageDigest(&first_storage);
1126 const first_ram_before_second = ramDigest(first_branch.ram);
1127 const first_receipt_before_second = try first_branch.quiescenceReceipt();
1128 const second_admission = try admission.prepare(
1129 second_basis,
1130 try admission.entropy(
1131 second_basis.frontiers.entropy_generation + 1,
1132 "branch-B",
1133 ),
1134 );
1135 const second_turn = try completeTurn(
1136 &second_branch,
1137 second_admission,
1138 @splat(0xb1),
1139 second_fence,
1140 );
1141 try std.testing.expectEqualSlices(
1142 u8,
1143 &first_storage_before_second,
1144 &storageDigest(&first_storage),
1145 );
1146 try std.testing.expectEqualSlices(
1147 u8,
1148 &first_ram_before_second,
1149 &ramDigest(first_branch.ram),
1150 );
1151 try std.testing.expectEqualDeep(
1152 first_receipt_before_second,
1153 try first_branch.quiescenceReceipt(),
1154 );
1155
1156 try std.testing.expect(!std.mem.eql(
1157 u8,
1158 &first_turn.receipt.delivery.digest,
1159 &second_turn.receipt.delivery.digest,
1160 ));
1161 try std.testing.expect(!std.mem.eql(
1162 u8,
1163 &first_turn.receipt.admission_receipt.digest,
1164 &second_turn.receipt.admission_receipt.digest,
1165 ));
1166 try std.testing.expectEqualSlices(
1167 u8,
1168 &first_turn.delivery.next_source_root,
1169 &first_turn.receipt.basis.source_root,
1170 );
1171 try std.testing.expectEqualSlices(
1172 u8,
1173 &second_turn.delivery.next_source_root,
1174 &second_turn.receipt.basis.source_root,
1175 );
1176 try std.testing.expect(!std.mem.eql(
1177 u8,
1178 &first_turn.receipt.basis.source_root,
1179 &second_turn.receipt.basis.source_root,
1180 ));
1181 try std.testing.expectEqual(
1182 first_turn.receipt.block_root.generation,
1183 second_turn.receipt.block_root.generation,
1184 );
1185 try std.testing.expect(!std.mem.eql(
1186 u8,
1187 &first_turn.receipt.block_root.digest,
1188 &second_turn.receipt.block_root.digest,
1189 ));
1190 try std.testing.expect(!std.mem.eql(
1191 u8,
1192 &first_turn.receipt.semantic_transcript_digest,
1193 &second_turn.receipt.semantic_transcript_digest,
1194 ));
1195 try std.testing.expectEqual(
1196 @as(u64, 1),
1197 first_turn.receipt.basis.frontiers.effect,
1198 );
1199 try std.testing.expectEqual(
1200 @as(u64, 1),
1201 second_turn.receipt.basis.frontiers.effect,
1202 );
1203 try std.testing.expectEqual(
1204 @as(u64, 1),
1205 first_turn.receipt.boundary.effect_frontier,
1206 );
1207 try std.testing.expectEqual(
1208 @as(u64, 1),
1209 second_turn.receipt.boundary.effect_frontier,
1210 );
1211 try std.testing.expect(first_turn.receipt.basis.outstanding_effect == null);
1212 try std.testing.expect(second_turn.receipt.basis.outstanding_effect == null);
1213
1214 const source_event = try os.abi.decodeEvent(&source_turn.events.storage[0]);
1215 const second_event = try os.abi.decodeEvent(&second_turn.events.storage[0]);
1216 const first_event_frontiers = try os.abi.EventRing.frontiers(
1217 layout.eventRing(first_branch.ram),
1218 first_fence,
1219 );
1220 const next_event_sequence = first_event_frontiers.produced + 1;
1221 var stale_source_event: os.abi.MessageWire = undefined;
1222 try os.abi.encodeEvent(
1223 selected.fence,
1224 next_event_sequence,
1225 source_event.header.correlation,
1226 source_event.value,
1227 &stale_source_event,
1228 );
1229 var stale_second_event: os.abi.MessageWire = undefined;
1230 try os.abi.encodeEvent(
1231 second_fence,
1232 next_event_sequence,
1233 second_event.header.correlation,
1234 second_event.value,
1235 &stale_second_event,
1236 );
1237 const first_before_stale = ramDigest(first_branch.ram);
1238 try std.testing.expectError(
1239 error.StaleActivation,
1240 os.abi.EventRing.push(
1241 layout.eventRing(first_branch.ram),
1242 first_fence,
1243 &stale_source_event,
1244 ),
1245 );
1246 try std.testing.expectEqualSlices(
1247 u8,
1248 &first_before_stale,
1249 &ramDigest(first_branch.ram),
1250 );
1251 try std.testing.expectError(
1252 error.StaleActivation,
1253 os.abi.EventRing.push(
1254 layout.eventRing(first_branch.ram),
1255 first_fence,
1256 &stale_second_event,
1257 ),
1258 );
1259 try std.testing.expectEqualSlices(
1260 u8,
1261 &first_before_stale,
1262 &ramDigest(first_branch.ram),
1263 );
1264 try std.testing.expectError(
1265 error.EffectRequestInvalid,
1266 admission.prepare(first_turn.receipt.basis, settled_record),
1267 );
1268 try std.testing.expectError(
1269 error.EffectRequestInvalid,
1270 admission.prepare(second_turn.receipt.basis, settled_record),
1271 );
1272
1273 var first_checkpoint_storage = checkpoint.Storage.init();
1274 const first_checkpoint = try first_branch.captureCheckpoint(
1275 &first_checkpoint_storage,
1276 &fixture.ram,
1277 );
1278 const first_root = try first_checkpoint.root();
1279 first_branch.deinit();
1280 first_closed = true;
1281 try std.testing.expectEqualSlices(
1282 u8,
1283 &reopened_storage_before,
1284 &ramDigest(&reopened_storage.bytes),
1285 );
1286 try std.testing.expectEqualSlices(
1287 u8,
1288 &reopened_ram_before,
1289 &ramDigest(&second_ram),
1290 );
1291 var second_checkpoint_storage = checkpoint.Storage.init();
1292 const second_checkpoint = try second_branch.captureCheckpoint(
1293 &second_checkpoint_storage,
1294 &second_ram,
1295 );
1296 const second_root = try second_checkpoint.root();
1297 try std.testing.expectEqualDeep(first_root.profile, second_root.profile);
1298 try std.testing.expect(!std.meta.eql(first_root.state, second_root.state));
1299 try std.testing.expect(!std.meta.eql(first_root, second_root));
1300 try std.testing.expectEqualSlices(
1301 u8,
1302 &[_]u8{
1303 0xd2, 0x9d, 0x94, 0x1d, 0x6d, 0xfa, 0xf2, 0x11,
1304 0x6b, 0xc6, 0x62, 0xd1, 0xd4, 0x3b, 0xf7, 0x78,
1305 0x60, 0xed, 0x0f, 0xae, 0xca, 0xe9, 0xe3, 0x16,
1306 0xb7, 0xaa, 0x68, 0x06, 0xa2, 0x4d, 0x7e, 0x55,
1307 },
1308 &first_root.digest,
1309 );
1310 try std.testing.expectEqualSlices(
1311 u8,
1312 &[_]u8{
1313 0xa1, 0x09, 0x54, 0xe6, 0xd7, 0x3a, 0xf9, 0x68,
1314 0x1d, 0x0f, 0x22, 0x2d, 0x8a, 0x43, 0x28, 0x16,
1315 0xa1, 0x96, 0xec, 0x05, 0x80, 0xb8, 0xa3, 0xec,
1316 0xfa, 0xf0, 0xc8, 0xf2, 0xe2, 0x1a, 0x48, 0x68,
1317 },
1318 &second_root.digest,
1319 );
1320 second_branch.deinit();
1321 second_closed = true;
1322 }
1323
1324 test "restore rejects absent corrupt mismatched and aliased components" {
1325 var checkpoint_storage = checkpoint.Storage.init();
1326 const captured = try captureAt(61, 0xe1, &checkpoint_storage, &first_ram);
1327 const expected_root = try captured.value.root();
1328 const supplied_fence: os.abi.ActivationFence = .{
1329 .world = @splat(0xe2),
1330 .generation = 1,
1331 .token = @splat(0xe3),
1332 };
1333 var restore_storage = instance.Storage.init();
1334 var restore_input: instance.RestoreInput = .{
1335 .checkpoint = .{ .durable = &captured.value },
1336 .expected_root = expected_root,
1337 .profile = profile.interpretedContinuationTestV1(),
1338 .execution_manifest = real_k0_manifest,
1339 .fence = supplied_fence,
1340 };
1341 @memset(&second_ram, 0xa5);
1342
1343 try expectRestoreError(
1344 error.RamBytesMismatch,
1345 instance.Instance.restore(
1346 &restore_storage,
1347 second_ram[0 .. second_ram.len - layout.page_bytes],
1348 restore_input,
1349 ),
1350 );
1351 const storage_in_ram: *instance.Storage = @ptrCast(
1352 @alignCast(second_ram[0..].ptr),
1353 );
1354 try expectRestoreError(
1355 error.StorageAliasesRam,
1356 instance.Instance.restore(storage_in_ram, &second_ram, restore_input),
1357 );
1358 comptime std.debug.assert(real_k0_manifest.len <= instance.storage_bytes);
1359 restore_input.execution_manifest =
1360 restore_storage.bytes[0..real_k0_manifest.len];
1361 try expectRestoreError(
1362 error.ManifestAliasesStorage,
1363 instance.Instance.restore(&restore_storage, &second_ram, restore_input),
1364 );
1365 restore_input.execution_manifest = real_k0_manifest;
1366
1367 var wrong_root = expected_root;
1368 wrong_root.digest[0] ^= 1;
1369 restore_input.expected_root = wrong_root;
1370 try expectRestoreError(
1371 error.CheckpointRootMismatch,
1372 instance.Instance.restore(&restore_storage, &second_ram, restore_input),
1373 );
1374
1375 restore_input.expected_root = expected_root;
1376 restore_input.profile = profile.interpretedReconstructV1();
1377 try expectRestoreError(
1378 error.CheckpointContractMismatch,
1379 instance.Instance.restore(&restore_storage, &second_ram, restore_input),
1380 );
1381 restore_input.profile = profile.interpretedContinuationTestV1();
1382 restore_input.fence.generation = 0;
1383 try expectRestoreError(
1384 error.InvalidActivationFence,
1385 instance.Instance.restore(&restore_storage, &second_ram, restore_input),
1386 );
1387 restore_input.fence = supplied_fence;
1388 restore_input.execution_manifest = &.{};
1389 try expectRestoreError(
1390 error.Truncated,
1391 instance.Instance.restore(&restore_storage, &second_ram, restore_input),
1392 );
1393 const fixture_image = fixture.elf();
1394 const fixture_execution = try fixture.execution(&fixture_image);
1395 restore_input.execution_manifest = fixture_execution.bytes();
1396 try expectRestoreError(
1397 error.ExecutionFingerprintMismatch,
1398 instance.Instance.restore(&restore_storage, &second_ram, restore_input),
1399 );
1400
1401 restore_input.execution_manifest = real_k0_manifest;
1402 const mutable = try firstWritableAddress(
1403 try os.boot.kernel.manifest.parse(real_k0_manifest),
1404 );
1405 first_ram[mutable] ^= 1;
1406 try expectRestoreError(
1407 error.MemoryDigestMismatch,
1408 instance.Instance.restore(&restore_storage, &second_ram, restore_input),
1409 );
1410 first_ram[mutable] ^= 1;
1411 const immutable: usize = @intCast(os.boot.kernel.physical_base);
1412 first_ram[immutable] ^= 1;
1413 try expectRestoreError(
1414 error.ImmutableImageMismatch,
1415 instance.Instance.restore(&restore_storage, &second_ram, restore_input),
1416 );
1417 first_ram[immutable] ^= 1;
1418
1419 var closed_storage = checkpoint.Storage.init();
1420 const closed_checkpoint: checkpoint.Checkpoint = .{
1421 .storage = &closed_storage,
1422 .ram = &third_ram,
1423 .root_digest = expected_root.digest,
1424 };
1425 restore_input.checkpoint = .{ .durable = &closed_checkpoint };
1426 try expectRestoreError(
1427 error.CheckpointClosed,
1428 instance.Instance.restore(&restore_storage, &second_ram, restore_input),
1429 );
1430
1431 restore_input.checkpoint = .{ .durable = &captured.value };
1432 restore_input.execution_manifest = second_ram[0..real_k0_manifest.len];
1433 try expectRestoreError(
1434 error.ManifestAliasesRam,
1435 instance.Instance.restore(&restore_storage, &second_ram, restore_input),
1436 );
1437 restore_input.execution_manifest = first_ram[0..real_k0_manifest.len];
1438 try expectRestoreError(
1439 error.ManifestAliasesCheckpoint,
1440 instance.Instance.restore(&restore_storage, &second_ram, restore_input),
1441 );
1442 restore_input.execution_manifest = real_k0_manifest;
1443 try expectRestoreError(
1444 error.RamAliasesCheckpoint,
1445 instance.Instance.restore(&restore_storage, &first_ram, restore_input),
1446 );
1447 comptime std.debug.assert(instance.storage_bytes <= checkpoint.ram_bytes);
1448 const aliased_storage: *instance.Storage = @ptrCast(
1449 @alignCast(first_ram[0..].ptr),
1450 );
1451 try expectRestoreError(
1452 error.StorageAliasesCheckpoint,
1453 instance.Instance.restore(aliased_storage, &second_ram, restore_input),
1454 );
1455 try expectFilled(&second_ram, 0xa5);
1456
1457 var restored = switch (instance.Instance.restore(
1458 &restore_storage,
1459 &second_ram,
1460 restore_input,
1461 )) {
1462 .ready => |value| value,
1463 .unavailable => return error.UnexpectedBackendUnavailable,
1464 .rejected => |failure| return failure,
1465 };
1466 defer restored.deinit();
1467 const active_storage_before = storageDigest(&restore_storage);
1468 try expectRestoreError(
1469 error.StorageInUse,
1470 instance.Instance.restore(&restore_storage, &third_ram, restore_input),
1471 );
1472 const active_storage_after = storageDigest(&restore_storage);
1473 try std.testing.expectEqualSlices(
1474 u8,
1475 &active_storage_before,
1476 &active_storage_after,
1477 );
1478 try captured.value.verify();
1479 }
1480
1481 test "checkpoint capture rejects every caller-owned storage alias" {
1482 comptime {
1483 std.debug.assert(checkpoint.storage_bytes <= layout.page_bytes);
1484 std.debug.assert(checkpoint.storage_alignment <= checkpoint.ram_alignment);
1485 std.debug.assert(checkpoint.storage_bytes <= instance.storage_bytes);
1486 std.debug.assert(checkpoint.storage_alignment <= instance.storage_alignment);
1487 }
1488 const execution = try os.boot.kernel.manifest.parse(real_k0_manifest);
1489 const selected = input(execution, 29, 0xd1);
1490 var live_storage = instance.Storage.init();
1491 var machine = try start(&live_storage, selected);
1492 defer machine.deinit();
1493 _ = try driveToReceipt(&machine, selected.fence);
1494
1495 const memory_storage: *checkpoint.Storage = @ptrCast(@alignCast(first_ram[0..].ptr));
1496 memory_storage.* = checkpoint.Storage.init();
1497 try std.testing.expectError(
1498 error.MemoryAliasesStorage,
1499 machine.captureCheckpoint(memory_storage, &first_ram),
1500 );
1501
1502 const boot_start: usize = @intCast(layout.boot_frame_address);
1503 const live_ram_storage: *checkpoint.Storage = @ptrCast(
1504 @alignCast(machine.ram[boot_start..].ptr),
1505 );
1506 try std.testing.expectError(
1507 error.StorageAliasesRam,
1508 machine.captureCheckpoint(live_ram_storage, &second_ram),
1509 );
1510
1511 const owner_storage: *checkpoint.Storage = @ptrCast(
1512 @alignCast(live_storage.bytes[0..].ptr),
1513 );
1514 try std.testing.expectError(
1515 error.StorageAliasesOwner,
1516 machine.captureCheckpoint(owner_storage, &second_ram),
1517 );
1518 }
1519
1520 fn captureAt(
1521 generation: u64,
1522 token: u8,
1523 storage: *checkpoint.Storage,
1524 destination: []align(checkpoint.ram_alignment) u8,
1525 ) !Captured {
1526 const execution = try os.boot.kernel.manifest.parse(real_k0_manifest);
1527 const selected = input(execution, generation, token);
1528 var live_storage = instance.Storage.init();
1529 var machine = try start(&live_storage, selected);
1530 defer machine.deinit();
1531 const receipt = try driveToReceipt(&machine, selected.fence);
1532 if (generation == 7) markTranslationActivity(machine.ram);
1533 const live_digest = ramDigest(machine.ram);
1534 const value = try machine.captureCheckpoint(storage, destination);
1535 try std.testing.expectEqualSlices(u8, &live_digest, &ramDigest(machine.ram));
1536 return .{ .value = value, .receipt = receipt };
1537 }
1538
1539 fn markTranslationActivity(ram: []u8) void {
1540 setEntryBits(ram, layout.pml4_address, 0, 1 << 5);
1541 setEntryBits(ram, layout.pdpt_address, 0, 1 << 5);
1542 for (0..layout.identity_large_pages) |index| {
1543 const value = entryValue(ram, layout.page_directory_address, index);
1544 setEntryBits(
1545 ram,
1546 layout.page_directory_address,
1547 index,
1548 (@as(u64, 1) << 5) |
1549 if (value & (@as(u64, 1) << 7) != 0)
1550 @as(u64, 1) << 6
1551 else
1552 0,
1553 );
1554 }
1555 const entries = layout.page_bytes / @sizeOf(u64);
1556 for (0..entries) |index| {
1557 const physical = @as(u64, index) * layout.page_bytes;
1558 if (!pageTableAddress(physical)) {
1559 setEntryBits(
1560 ram,
1561 layout.low_page_table_address,
1562 index,
1563 (1 << 5) | (1 << 6),
1564 );
1565 }
1566 }
1567 for (0..layout.kernel_page_table_count) |table_index| {
1568 const address = layout.kernel_page_table_base +
1569 @as(u64, table_index) * layout.page_bytes;
1570 for (0..entries) |index| {
1571 const value = entryValue(ram, address, index);
1572 setEntryBits(
1573 ram,
1574 address,
1575 index,
1576 (@as(u64, 1) << 5) |
1577 if (value & (@as(u64, 1) << 1) != 0)
1578 @as(u64, 1) << 6
1579 else
1580 0,
1581 );
1582 }
1583 }
1584 }
1585
1586 fn pageTableAddress(address: u64) bool {
1587 for (layout.page_table_addresses) |candidate| {
1588 if (address == candidate) return true;
1589 }
1590 return false;
1591 }
1592
1593 fn setEntryBits(ram: []u8, address: u64, index: usize, mask: u64) void {
1594 const offset: usize = @intCast(address + index * @sizeOf(u64));
1595 std.mem.writeInt(
1596 u64,
1597 ram[offset..][0..@sizeOf(u64)],
1598 entryValue(ram, address, index) | mask,
1599 .little,
1600 );
1601 }
1602
1603 fn entryValue(ram: []const u8, address: u64, index: usize) u64 {
1604 const offset: usize = @intCast(address + index * @sizeOf(u64));
1605 return std.mem.readInt(u64, ram[offset..][0..@sizeOf(u64)], .little);
1606 }
1607
1608 fn toggleEntryBits(ram: []u8, address: u64, index: usize, mask: u64) void {
1609 const offset: usize = @intCast(address + index * @sizeOf(u64));
1610 const value = std.mem.readInt(u64, ram[offset..][0..@sizeOf(u64)], .little);
1611 std.mem.writeInt(u64, ram[offset..][0..@sizeOf(u64)], value ^ mask, .little);
1612 }
1613
1614 fn ramDigest(ram: []const u8) os.abi.Digest {
1615 var digest: os.abi.Digest = undefined;
1616 std.crypto.hash.sha2.Sha256.hash(ram, &digest, .{});
1617 return digest;
1618 }
1619
1620 fn driveToReceipt(
1621 machine: *instance.Instance,
1622 fence: os.abi.ActivationFence,
1623 ) !instance.QuiescenceReceipt {
1624 return driveTerminalToReceipt(machine, fence, 0);
1625 }
1626
1627 fn activate(machine: *instance.Instance) !instance.EventBatch {
1628 const ready = try machine.run();
1629 try std.testing.expectEqual(
1630 os.abi.channel.DoorbellCode.ready,
1631 ready.doorbell.code,
1632 );
1633 var batch: instance.EventBatch = undefined;
1634 try machine.takeEvents(&batch);
1635 try std.testing.expectEqual(
1636 @as(usize, os.k0.events_per_activation),
1637 batch.frames().len,
1638 );
1639 return batch;
1640 }
1641
1642 fn completeTurn(
1643 machine: *instance.Instance,
1644 admitted: admission.Admission,
1645 next_source_root: os.abi.Digest,
1646 fence: os.abi.ActivationFence,
1647 ) !Turn {
1648 const delivery = try admission.bindDelivery(
1649 admitted,
1650 next_source_root,
1651 fence,
1652 );
1653 try machine.deliverAdmitted(&delivery);
1654 const quiescent = try machine.run();
1655 try std.testing.expectEqual(
1656 os.abi.channel.DoorbellCode.quiescent,
1657 quiescent.doorbell.code,
1658 );
1659 var events: instance.EventBatch = undefined;
1660 try machine.takeEvents(&events);
1661 try machine.acknowledge(delivery.receipt);
1662 return .{
1663 .delivery = delivery,
1664 .events = events,
1665 .receipt = try machine.quiescenceReceipt(),
1666 };
1667 }
1668
1669 fn restore(
1670 storage: *instance.Storage,
1671 ram: []align(instance.ram_alignment) u8,
1672 source: checkpoint.Checkpoint,
1673 expected_root: checkpoint.Root,
1674 fence: os.abi.ActivationFence,
1675 ) !instance.Instance {
1676 return switch (instance.Instance.restore(storage, ram, .{
1677 .checkpoint = .{ .durable = &source },
1678 .expected_root = expected_root,
1679 .profile = profile.interpretedContinuationTestV1(),
1680 .execution_manifest = real_k0_manifest,
1681 .fence = fence,
1682 })) {
1683 .ready => |value| value,
1684 .unavailable => error.UnexpectedBackendUnavailable,
1685 .rejected => |failure| failure,
1686 };
1687 }
1688
1689 fn expectRestartFrame(
1690 machine: *const instance.Instance,
1691 receipt: instance.QuiescenceReceipt,
1692 fence: os.abi.ActivationFence,
1693 ) !void {
1694 const boot_start: usize = @intCast(layout.boot_frame_address);
1695 const wire: *const os.abi.BootWire = @ptrCast(
1696 machine.ram[boot_start..][0..os.abi.boot.frame_bytes].ptr,
1697 );
1698 const frame = try os.abi.decodeBootFrame(wire);
1699 try std.testing.expectEqualDeep(fence, frame.fence);
1700 try std.testing.expectEqual(
1701 @as(u32, @intCast(receipt.block_root.generation)),
1702 frame.restart.root_generation,
1703 );
1704 try std.testing.expectEqual(receipt.k0.counter, frame.restart.counter);
1705 try std.testing.expectEqual(
1706 receipt.boundary.semantic_frontier,
1707 frame.restart.semantic_frontier,
1708 );
1709 try std.testing.expectEqual(
1710 receipt.basis.frontiers.input,
1711 frame.input_frontier,
1712 );
1713 try std.testing.expectEqual(
1714 receipt.boundary.terminal_offset,
1715 frame.terminal_offset,
1716 );
1717 }
1718
1719 fn expectEquivalentEvents(
1720 first: instance.EventBatch,
1721 second: instance.EventBatch,
1722 fence: os.abi.ActivationFence,
1723 ) !void {
1724 var normalized_first: instance.EventBatch = undefined;
1725 var normalized_second: instance.EventBatch = undefined;
1726 try normalizeBatch(first, fence, &normalized_first);
1727 try normalizeBatch(second, fence, &normalized_second);
1728 try std.testing.expectEqualDeep(normalized_first, normalized_second);
1729 }
1730
1731 fn normalizeBatch(
1732 source: instance.EventBatch,
1733 fence: os.abi.ActivationFence,
1734 output: *instance.EventBatch,
1735 ) !void {
1736 var normalized: instance.EventBatch = .{
1737 .count = source.count,
1738 .storage = @splat(@splat(0)),
1739 };
1740 for (source.frames(), 0..) |*frame, index| {
1741 const decoded = try os.abi.decodeEvent(frame);
1742 var value = decoded.value;
1743 if (std.meta.activeTag(value) == .quiescent) {
1744 value.quiescent.capability_generation = fence.generation;
1745 }
1746 try os.abi.encodeEvent(
1747 fence,
1748 decoded.header.sequence,
1749 decoded.header.correlation,
1750 value,
1751 &normalized.storage[index],
1752 );
1753 }
1754 output.* = normalized;
1755 }
1756
1757 fn encodeCheckpointStream(value: *const checkpoint.Checkpoint) !void {
1758 var writer = std.Io.Writer.fixed(
1759 checkpoint_stream[0..checkpoint.stream.stream_bytes],
1760 );
1761 try checkpoint.stream.encodeDisjoint(value, &writer);
1762 try std.testing.expectEqual(checkpoint.stream.stream_bytes, writer.end);
1763 }
1764
1765 fn expectExactCheckpointStream(value: *const checkpoint.Checkpoint) !void {
1766 var comparing = ComparingWriter.init(
1767 checkpoint_stream[0..checkpoint.stream.stream_bytes],
1768 4093,
1769 );
1770 comparing.attach();
1771 try checkpoint.stream.encodeDisjoint(value, &comparing.writer);
1772 try std.testing.expect(!comparing.mismatch);
1773 try std.testing.expectEqual(
1774 checkpoint.stream.stream_bytes,
1775 comparing.offset,
1776 );
1777 }
1778
1779 fn streamDigest() os.abi.Digest {
1780 return ramDigest(checkpoint_stream[0..checkpoint.stream.stream_bytes]);
1781 }
1782
1783 fn expectStreamDecodeFailure(
1784 expected: anyerror,
1785 bytes: []const u8,
1786 expected_root: checkpoint.Root,
1787 storage: *checkpoint.Storage,
1788 ram: []align(checkpoint.ram_alignment) u8,
1789 ) !void {
1790 var reader = std.Io.Reader.fixed(bytes);
1791 try std.testing.expectError(
1792 expected,
1793 checkpoint.stream.decodeDisjoint(
1794 &reader,
1795 expected_root,
1796 storage,
1797 ram,
1798 ),
1799 );
1800 switch (try checkpoint.recoverAfterCrash(storage, ram, null)) {
1801 .empty => {},
1802 .published => return error.TestExpectedEmptyCheckpoint,
1803 }
1804 }
1805
1806 fn expectStreamReadFailure(
1807 failure_offset: usize,
1808 expected_root: checkpoint.Root,
1809 storage: *checkpoint.Storage,
1810 ram: []align(checkpoint.ram_alignment) u8,
1811 ) !void {
1812 var reader = FragmentedReader.init(
1813 checkpoint_stream[0..checkpoint.stream.stream_bytes],
1814 4093,
1815 );
1816 reader.failure_offset = failure_offset;
1817 reader.attachUnbuffered();
1818 try std.testing.expectError(
1819 error.ReadFailed,
1820 checkpoint.stream.decodeDisjoint(
1821 &reader.reader,
1822 expected_root,
1823 storage,
1824 ram,
1825 ),
1826 );
1827 switch (try checkpoint.recoverAfterCrash(storage, ram, null)) {
1828 .empty => {},
1829 .published => return error.TestExpectedEmptyCheckpoint,
1830 }
1831 }
1832
1833 fn storageDigest(storage: *const instance.Storage) os.abi.Digest {
1834 var digest: os.abi.Digest = undefined;
1835 std.crypto.hash.sha2.Sha256.hash(&storage.bytes, &digest, .{});
1836 return digest;
1837 }
1838
1839 fn expectRestoreError(
1840 expected: anyerror,
1841 result: instance.RestoreResult,
1842 ) !void {
1843 switch (result) {
1844 .ready => |value| {
1845 var restored = value;
1846 restored.deinit();
1847 return error.TestExpectedRestoreRejection;
1848 },
1849 .unavailable => return error.TestExpectedRestoreRejection,
1850 .rejected => |actual| try std.testing.expectEqual(expected, actual),
1851 }
1852 }
1853
1854 fn firstWritableAddress(execution: os.boot.kernel.manifest.View) !usize {
1855 var index: u16 = 0;
1856 while (index < execution.header.load_count) : (index += 1) {
1857 const load = execution.load(index);
1858 if (load.flags & os.boot.kernel.manifest.load_flag_write == 0) continue;
1859 return @intCast(
1860 execution.header.facts.physical_base + load.physical_offset,
1861 );
1862 }
1863 return error.TestExpectedWritableLoad;
1864 }
1865
1866 fn expectFilled(bytes: []const u8, expected: u8) !void {
1867 try std.testing.expect(std.mem.allEqual(u8, bytes, expected));
1868 }
1869
1870 fn driveTerminalToReceipt(
1871 machine: *instance.Instance,
1872 fence: os.abi.ActivationFence,
1873 offset: u64,
1874 ) !instance.QuiescenceReceipt {
1875 const ready = try machine.run();
1876 try std.testing.expectEqual(
1877 os.abi.channel.DoorbellCode.ready,
1878 ready.doorbell.code,
1879 );
1880 var batch: instance.EventBatch = undefined;
1881 try machine.takeEvents(&batch);
1882 const admitted = try admission.prepare(
1883 try machine.admissionBasis(),
1884 try admission.terminal(offset, os.k0.request_bytes),
1885 );
1886 const delivery = try admission.bindDelivery(admitted, @splat(0x88), fence);
1887 try machine.deliverAdmitted(&delivery);
1888 const quiescent = try machine.run();
1889 try std.testing.expectEqual(
1890 os.abi.channel.DoorbellCode.quiescent,
1891 quiescent.doorbell.code,
1892 );
1893 try machine.takeEvents(&batch);
1894 try machine.acknowledge(delivery.receipt);
1895 return machine.quiescenceReceipt();
1896 }
1897
1898 fn start(
1899 storage: *instance.Storage,
1900 selected: instance.Input,
1901 ) !instance.Instance {
1902 return switch (instance.Instance.init(storage, &fixture.ram, selected)) {
1903 .ready => |value| value,
1904 .unavailable => error.UnexpectedBackendUnavailable,
1905 .rejected => |failure| failure,
1906 };
1907 }
1908
1909 fn input(
1910 execution: os.boot.kernel.manifest.View,
1911 generation: u64,
1912 token: u8,
1913 ) instance.Input {
1914 return .{
1915 .profile = profile.interpretedContinuationTestV1(),
1916 .elf = real_k0_elf,
1917 .execution_manifest = real_k0_manifest,
1918 .expected_execution_fingerprint = execution.header.fingerprint,
1919 .fence = .{
1920 .world = @splat(0x44),
1921 .generation = generation,
1922 .token = @splat(token),
1923 },
1924 .initial_time_tick = 7,
1925 .entropy_generation = 1,
1926 .terminal_offset = 0,
1927 .effect_frontier = 0,
1928 .block_root = @splat(0x33),
1929 .source_root = @splat(0x77),
1930 .input_frontier = 0,
1931 .terminal_input_offset = 0,
1932 .outstanding_effect = null,
1933 };
1934 }
1935
1936 fn expectZero(bytes: []const u8) !void {
1937 for (bytes) |byte| try std.testing.expectEqual(@as(u8, 0), byte);
1938 }