lib/machine/src/checkpoint/roots/manifest.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const canon = @import("../canon/root.zig");
2 const os = @import("os");
3 const owner = @import("../owner/root.zig");
4 const std = @import("std");
5 const stream = @import("../stream/root.zig");
6 const types = @import("types.zig");
7
8 const Sha256 = std.crypto.hash.sha2.Sha256;
9 const domain = "TINYMACHINECHECKPOINTMANIFEST2\x00";
10 const magic = [8]u8{ 'M', 'C', 'H', 'R', 'O', 'O', 'T', '2' };
11 const version: u16 = 2;
12 const format: u16 = 2;
13 const parent_flag: u16 = 1;
14
15 const Layout = struct {
16 const version_offset: usize = 8;
17 const bytes_offset: usize = 10;
18 const flags_offset: usize = 12;
19 const format_offset: usize = 14;
20 const machine_offset: usize = 16;
21 const profile_offset: usize = 48;
22 const state_offset: usize = 80;
23 const memory_offset: usize = 112;
24 const pages_offset: usize = 144;
25 const block_offset: usize = 176;
26 const generation_offset: usize = 208;
27 const parent_offset: usize = 216;
28 const dirty_pages_offset: usize = 248;
29 const reserved_offset: usize = 250;
30 const header_offset: usize = 256;
31 };
32
33 const References = struct {
34 machine: owner.Root,
35 memory: owner.MemoryDigest,
36 pages: types.PageRoot,
37 block: os.abi.BlockRoot,
38 parent: ?types.ManifestRoot,
39 dirty_pages: u16,
40 };
41
42 /// Encodes one manifest and stages it in an open publication transaction. Every
43 /// one of the 4352 encoded bytes feeds the manifest root digest. The call then
44 /// reads the staged bytes back and requires them to decode to the supplied
45 /// value, returning `ManifestCorrupt` when they differ.
46 pub fn stage(
47 storage: types.Storage,
48 value: types.Manifest,
49 ) types.Error!types.ManifestRoot {
50 var bytes: [types.manifest_bytes]u8 = undefined;
51 encode(value, &bytes);
52 const expected = manifestRoot(&bytes);
53 try storage.put(.manifest, expected.digest, &bytes);
54 var reopened: [types.manifest_bytes]u8 = undefined;
55 try storage.readStaged(.manifest, expected.digest, &reopened);
56 const actual = try decodeRooted(&reopened, expected);
57 if (!std.meta.eql(value, actual)) return error.ManifestCorrupt;
58 return expected;
59 }
60
61 /// Reads one committed manifest under `expected` and authenticates its encoded
62 /// bytes. The function requires the checkpoint root to equal the digest
63 /// recomputed from its own profile and state digests, and the memory digest to
64 /// equal the one recomputed from the page root. The check also requires the
65 /// embedded header to decode under the same checkpoint root and to carry the
66 /// same memory digest and block root. The call returns the decoded restore
67 /// material.
68 pub fn read(
69 storage: types.Storage,
70 expected: types.ManifestRoot,
71 ) types.Error!types.Manifest {
72 try validateDigest(expected.digest);
73 var bytes: [types.manifest_bytes]u8 = undefined;
74 try storage.read(.manifest, expected.digest, &bytes);
75 return decodeRooted(&bytes, expected);
76 }
77
78 fn encode(
79 value: types.Manifest,
80 output: *[types.manifest_bytes]u8,
81 ) void {
82 var bytes: [types.manifest_bytes]u8 = @splat(0);
83 @memcpy(bytes[0..magic.len], &magic);
84 put16(&bytes, Layout.version_offset, version);
85 put16(&bytes, Layout.bytes_offset, types.manifest_bytes);
86 put16(
87 &bytes,
88 Layout.flags_offset,
89 if (value.parent == null) 0 else parent_flag,
90 );
91 put16(&bytes, Layout.format_offset, format);
92 putDigest(&bytes, Layout.machine_offset, value.machine.digest);
93 putDigest(&bytes, Layout.profile_offset, value.machine.profile.digest);
94 putDigest(&bytes, Layout.state_offset, value.machine.state.digest);
95 putDigest(&bytes, Layout.memory_offset, value.memory.digest);
96 putDigest(&bytes, Layout.pages_offset, value.pages.digest);
97 putDigest(&bytes, Layout.block_offset, value.block.digest);
98 put64(&bytes, Layout.generation_offset, value.block.generation);
99 if (value.parent) |parent| {
100 putDigest(&bytes, Layout.parent_offset, parent.digest);
101 }
102 put16(&bytes, Layout.dirty_pages_offset, value.dirty_pages);
103 stream.encodeHeader(.{
104 .root = value.machine,
105 .memory = value.memory,
106 .material = value.material,
107 }, header(&bytes));
108 output.* = bytes;
109 }
110
111 fn decodeRooted(
112 input: *const [types.manifest_bytes]u8,
113 expected: types.ManifestRoot,
114 ) types.Error!types.Manifest {
115 const actual = manifestRoot(input);
116 if (!sameDigest(actual.digest, expected.digest)) {
117 return error.ManifestRootMismatch;
118 }
119 try validateEnvelope(input);
120 const flags = get16(input, Layout.flags_offset);
121 const references: References = .{
122 .machine = .{
123 .digest = digest(input, Layout.machine_offset),
124 .profile = .{ .digest = digest(input, Layout.profile_offset) },
125 .state = .{ .digest = digest(input, Layout.state_offset) },
126 },
127 .memory = .{ .digest = digest(input, Layout.memory_offset) },
128 .pages = .{ .digest = digest(input, Layout.pages_offset) },
129 .block = .{
130 .generation = get64(input, Layout.generation_offset),
131 .digest = digest(input, Layout.block_offset),
132 },
133 .parent = if ((flags & parent_flag) == 0)
134 null
135 else
136 .{ .digest = digest(input, Layout.parent_offset) },
137 .dirty_pages = get16(input, Layout.dirty_pages_offset),
138 };
139 try validateReferences(references, expected);
140 const decoded = stream.decodeHeader(
141 headerConst(input),
142 references.machine,
143 ) catch return error.ManifestCorrupt;
144 if (!std.meta.eql(decoded.memory, references.memory) or
145 !std.meta.eql(decoded.material.receipt.block_root, references.block))
146 {
147 return error.ManifestCorrupt;
148 }
149 const identity = try owner.identify(decoded.material, decoded.memory);
150 if (!std.meta.eql(identity.root, references.machine)) {
151 return error.ManifestCorrupt;
152 }
153 return .{
154 .machine = references.machine,
155 .memory = references.memory,
156 .pages = references.pages,
157 .block = references.block,
158 .material = decoded.material,
159 .parent = references.parent,
160 .dirty_pages = references.dirty_pages,
161 };
162 }
163
164 fn validateEnvelope(
165 input: *const [types.manifest_bytes]u8,
166 ) types.Error!void {
167 if (!std.mem.eql(u8, input[0..magic.len], &magic) or
168 get16(input, Layout.version_offset) != version or
169 get16(input, Layout.bytes_offset) != types.manifest_bytes or
170 get16(input, Layout.flags_offset) & ~parent_flag != 0 or
171 get16(input, Layout.format_offset) != format or
172 ((get16(input, Layout.flags_offset) & parent_flag) == 0 and
173 !os.abi.wire.allZero(
174 input[Layout.parent_offset..Layout.dirty_pages_offset],
175 )) or
176 !os.abi.wire.allZero(
177 input[Layout.reserved_offset..Layout.header_offset],
178 ))
179 {
180 return error.ManifestCorrupt;
181 }
182 }
183
184 fn validateReferences(
185 value: References,
186 expected: types.ManifestRoot,
187 ) types.Error!void {
188 try validateDigest(value.machine.digest);
189 try validateDigest(value.machine.profile.digest);
190 try validateDigest(value.machine.state.digest);
191 try validateDigest(value.memory.digest);
192 try validateDigest(value.pages.digest);
193 try validateDigest(value.block.digest);
194 if (value.parent) |parent| {
195 try validateDigest(parent.digest);
196 if (sameDigest(parent.digest, expected.digest) or
197 value.dirty_pages > types.page_count)
198 {
199 return error.ManifestCorrupt;
200 }
201 } else if (value.dirty_pages != types.page_count) {
202 return error.ManifestCorrupt;
203 }
204 if (value.block.generation == 0 or
205 !sameDigest(
206 value.machine.digest,
207 canon.machine(value.machine.profile.digest, value.machine.state.digest),
208 ) or
209 !sameDigest(value.memory.digest, canon.memory(value.pages.digest)))
210 {
211 return error.ManifestCorrupt;
212 }
213 }
214
215 fn header(output: *[types.manifest_bytes]u8) *[stream.header_bytes]u8 {
216 return @ptrCast(output[Layout.header_offset..][0..stream.header_bytes]);
217 }
218
219 fn headerConst(
220 input: *const [types.manifest_bytes]u8,
221 ) *const [stream.header_bytes]u8 {
222 return @ptrCast(input[Layout.header_offset..][0..stream.header_bytes]);
223 }
224
225 fn manifestRoot(
226 bytes: *const [types.manifest_bytes]u8,
227 ) types.ManifestRoot {
228 var hasher = Sha256.init(.{});
229 hasher.update(domain);
230 hasher.update(bytes);
231 var output: os.abi.Digest = undefined;
232 hasher.final(&output);
233 return .{ .digest = output };
234 }
235
236 fn validateDigest(value: os.abi.Digest) types.Error!void {
237 os.abi.wire.validateDigest(value) catch return error.ManifestCorrupt;
238 }
239
240 fn digest(
241 input: *const [types.manifest_bytes]u8,
242 offset: usize,
243 ) os.abi.Digest {
244 return input[offset..][0..os.abi.wire.digest_bytes].*;
245 }
246
247 fn putDigest(
248 output: *[types.manifest_bytes]u8,
249 offset: usize,
250 value: os.abi.Digest,
251 ) void {
252 @memcpy(output[offset..][0..os.abi.wire.digest_bytes], &value);
253 }
254
255 fn put16(output: *[types.manifest_bytes]u8, offset: usize, value: u16) void {
256 os.abi.wire.write16(output[offset..][0..2], value);
257 }
258
259 fn put64(output: *[types.manifest_bytes]u8, offset: usize, value: u64) void {
260 os.abi.wire.write64(output[offset..][0..8], value);
261 }
262
263 fn get16(input: *const [types.manifest_bytes]u8, offset: usize) u16 {
264 return os.abi.wire.read16(input[offset..][0..2]);
265 }
266
267 fn get64(input: *const [types.manifest_bytes]u8, offset: usize) u64 {
268 return os.abi.wire.read64(input[offset..][0..8]);
269 }
270
271 fn sameDigest(left: os.abi.Digest, right: os.abi.Digest) bool {
272 return std.mem.eql(u8, &left, &right);
273 }
274
275 comptime {
276 std.debug.assert(Layout.reserved_offset < Layout.header_offset);
277 std.debug.assert(
278 Layout.header_offset + stream.header_bytes == types.manifest_bytes,
279 );
280 std.debug.assert(os.abi.wire.digest_bytes == 32);
281 }