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 }