lib/machine/src/world/source.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const restore = @import("restore.zig");
2 const std = @import("std");
3
4 /// Rejections of replay sources, so a caller matches on them to tell an overlapping
5 /// buffer apart from a miscounted one. `SourceAlias` reports that a reader or a
6 /// byte range lies on top of memory the world is using.
7 pub const Error = error{
8 RestoredShapeMismatch,
9 SourceAlias,
10 SourceCountMismatch,
11 };
12
13 const source_limit: usize = 2;
14
15 /// Rejects a replay reader that overlaps the restored world, its ledger, instance
16 /// storage, guest RAM, or another reader. Every replay entry point calls this before
17 /// it reads a byte, because a reader over live memory would see the change it is
18 /// verifying. The call accepts one or two readers. A reader whose own value overlaps
19 /// its buffer is rejected too.
20 pub fn validate(
21 restored: *restore.Restored,
22 readers: []const *std.Io.Reader,
23 ) Error!void {
24 if (readers.len == 0 or readers.len > source_limit) {
25 return error.SourceCountMismatch;
26 }
27 if (restored.node_count > restored.nodes.len) {
28 return error.RestoredShapeMismatch;
29 }
30 for (readers, 0..) |reader, index| {
31 if (buffersOverlap(reader.buffer, std.mem.asBytes(reader)) or
32 readerAliases(reader, std.mem.asBytes(restored)) or
33 readerAliases(reader, std.mem.asBytes(restored.fabric)))
34 {
35 return error.SourceAlias;
36 }
37 for (readers[0..index]) |previous| {
38 if (readerAliases(reader, std.mem.asBytes(previous)) or
39 readerAliases(previous, std.mem.asBytes(reader)) or
40 buffersOverlap(reader.buffer, previous.buffer))
41 {
42 return error.SourceAlias;
43 }
44 }
45 for (&restored.nodes) |*node| {
46 const machine = if (node.machine) |*value| value else continue;
47 if (readerAliases(reader, &machine.storage.bytes) or
48 readerAliases(reader, machine.ram))
49 {
50 return error.SourceAlias;
51 }
52 }
53 }
54 }
55
56 /// Rejects a byte range that overlaps the restored world, its ledger, or any instance
57 /// storage or guest RAM, so a caller holding a plan or a buffer gets the same protection.
58 pub fn validateMemory(
59 restored: *restore.Restored,
60 bytes: []const u8,
61 ) Error!void {
62 if (restored.node_count > restored.nodes.len) {
63 return error.RestoredShapeMismatch;
64 }
65 if (memoryAliases(restored, bytes)) return error.SourceAlias;
66 }
67
68 fn memoryAliases(
69 restored: *restore.Restored,
70 bytes: []const u8,
71 ) bool {
72 if (buffersOverlap(bytes, std.mem.asBytes(restored)) or
73 buffersOverlap(bytes, std.mem.asBytes(restored.fabric)))
74 {
75 return true;
76 }
77 for (&restored.nodes) |*node| {
78 const machine = if (node.machine) |*value| value else continue;
79 if (buffersOverlap(bytes, &machine.storage.bytes) or
80 buffersOverlap(bytes, machine.ram))
81 {
82 return true;
83 }
84 }
85 return false;
86 }
87
88 fn readerAliases(reader: *std.Io.Reader, bytes: []const u8) bool {
89 return buffersOverlap(std.mem.asBytes(reader), bytes) or
90 buffersOverlap(reader.buffer, bytes);
91 }
92
93 fn buffersOverlap(left: []const u8, right: []const u8) bool {
94 if (left.len == 0 or right.len == 0) return false;
95 const left_start = @intFromPtr(left.ptr);
96 const right_start = @intFromPtr(right.ptr);
97 const left_end = std.math.add(usize, left_start, left.len) catch return true;
98 const right_end = std.math.add(usize, right_start, right.len) catch return true;
99 return left_start < right_end and right_start < left_end;
100 }