lib/machine/src/instance/provenance.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const os = @import("os");
2 const std = @import("std");
3 const memory = @import("memory.zig");
4
5 const manifest = os.boot.kernel.manifest;
6 const Sha256 = std.crypto.hash.sha2.Sha256;
7 const immutable_domain = "TINYMACHINEIMMUTABLE1\x00";
8
9 pub const Error = error{
10 ImmutableDescriptorInvalid,
11 ImmutableImageMismatch,
12 ImmutableLoadOutOfBounds,
13 };
14
15 pub const ImmutableLoad = struct {
16 source_offset: u32 = 0,
17 file_bytes: u32 = 0,
18 memory_bytes: u32 = 0,
19 physical_offset: u32 = 0,
20 virtual_offset: u64 = 0,
21 flags: u32 = 0,
22 };
23
24 pub const ImmutableImage = struct {
25 digest: manifest.Digest,
26 initial: manifest.InitialState,
27 entry_offset: u32,
28 load_count: u16,
29 loads: [manifest.loads_max]ImmutableLoad,
30 };
31
32 pub fn fromSource(
33 execution: manifest.View,
34 image: []const u8,
35 ) ImmutableImage {
36 var result: ImmutableImage = .{
37 .digest = undefined,
38 .initial = execution.header.facts.initial,
39 .entry_offset = execution.header.facts.entry_offset,
40 .load_count = 0,
41 .loads = @splat(.{}),
42 };
43 var hasher = immutableHasher(result.entry_offset, immutableCount(execution));
44 const zeros: [4096]u8 = @splat(0);
45 var index: u16 = 0;
46 while (index < execution.header.load_count) : (index += 1) {
47 const load = execution.load(index);
48 if (load.flags & manifest.load_flag_write != 0) continue;
49 const retained = immutableLoad(load);
50 result.loads[result.load_count] = retained;
51 result.load_count += 1;
52 hashLoad(&hasher, retained);
53 const start: usize = @intCast(load.input_offset);
54 const end = start + @as(usize, load.file_bytes);
55 std.debug.assert(end <= image.len);
56 hasher.update(image[start..end]);
57 var remaining = load.memory_bytes - load.file_bytes;
58 while (remaining > 0) {
59 const count: usize = @min(@as(usize, remaining), zeros.len);
60 hasher.update(zeros[0..count]);
61 remaining -= @intCast(count);
62 }
63 }
64 result.digest = finishDigest(&hasher);
65 validate(result) catch unreachable;
66 return result;
67 }
68
69 pub fn validateRam(
70 image: ImmutableImage,
71 ram: []const u8,
72 ) Error!void {
73 try validate(image);
74 var hasher = immutableHasher(image.entry_offset, image.load_count);
75 for (image.loads[0..image.load_count]) |load| {
76 if (load.flags & manifest.load_flag_write != 0 or
77 load.file_bytes > load.memory_bytes)
78 {
79 return error.ImmutableLoadOutOfBounds;
80 }
81 hashLoad(&hasher, load);
82 const start = std.math.add(
83 u64,
84 os.boot.kernel.physical_base,
85 load.physical_offset,
86 ) catch return error.ImmutableLoadOutOfBounds;
87 const end = std.math.add(
88 u64,
89 start,
90 load.memory_bytes,
91 ) catch return error.ImmutableLoadOutOfBounds;
92 if (end > ram.len) return error.ImmutableLoadOutOfBounds;
93 hasher.update(ram[@intCast(start)..@intCast(end)]);
94 }
95 const actual = finishDigest(&hasher);
96 if (!std.mem.eql(u8, &actual, &image.digest)) {
97 return error.ImmutableImageMismatch;
98 }
99 }
100
101 pub fn validateAccess(
102 image: ImmutableImage,
103 access: memory.Access,
104 ) (Error || memory.Error)!void {
105 try validate(image);
106 var hasher = immutableHasher(image.entry_offset, image.load_count);
107 var scratch: [memory.page_bytes]u8 = undefined;
108 for (image.loads[0..image.load_count]) |load| {
109 if (load.flags & manifest.load_flag_write != 0 or
110 load.file_bytes > load.memory_bytes)
111 {
112 return error.ImmutableLoadOutOfBounds;
113 }
114 hashLoad(&hasher, load);
115 const start = std.math.add(
116 u64,
117 os.boot.kernel.physical_base,
118 load.physical_offset,
119 ) catch return error.ImmutableLoadOutOfBounds;
120 const end = std.math.add(
121 u64,
122 start,
123 load.memory_bytes,
124 ) catch return error.ImmutableLoadOutOfBounds;
125 if (end > memory.ram_bytes) return error.ImmutableLoadOutOfBounds;
126 var cursor: usize = @intCast(start);
127 const limit: usize = @intCast(end);
128 while (cursor < limit) {
129 const count = @min(scratch.len, limit - cursor);
130 try access.read(cursor, scratch[0..count]);
131 hasher.update(scratch[0..count]);
132 cursor += count;
133 }
134 }
135 const actual = finishDigest(&hasher);
136 if (!std.mem.eql(u8, &actual, &image.digest)) {
137 return error.ImmutableImageMismatch;
138 }
139 }
140
141 pub fn validate(image: ImmutableImage) Error!void {
142 if (image.load_count == 0 or image.load_count > image.loads.len) {
143 return error.ImmutableDescriptorInvalid;
144 }
145 var previous_source_end: u64 = 0;
146 var previous_memory_end: u64 = 0;
147 var entry_found = false;
148 for (image.loads[0..image.load_count], 0..) |load, index| {
149 if (load.memory_bytes == 0 or
150 load.file_bytes > load.memory_bytes or
151 load.flags & ~manifest.load_flags_valid != 0 or
152 load.flags & manifest.load_flag_write != 0 or
153 load.virtual_offset != load.physical_offset)
154 {
155 return error.ImmutableDescriptorInvalid;
156 }
157 const source_end = std.math.add(
158 u64,
159 load.source_offset,
160 load.file_bytes,
161 ) catch return error.ImmutableDescriptorInvalid;
162 const memory_end = std.math.add(
163 u64,
164 load.virtual_offset,
165 load.memory_bytes,
166 ) catch return error.ImmutableDescriptorInvalid;
167 const file_end = std.math.add(
168 u64,
169 load.virtual_offset,
170 load.file_bytes,
171 ) catch return error.ImmutableDescriptorInvalid;
172 if (source_end > os.boot.kernel.kernel_file_bytes_max or
173 memory_end > os.boot.kernel.kernel_memory_bytes_max or
174 index > 0 and (load.source_offset < previous_source_end or
175 load.virtual_offset < previous_memory_end))
176 {
177 return error.ImmutableDescriptorInvalid;
178 }
179 if (load.flags & manifest.load_flag_execute != 0 and
180 image.entry_offset >= load.virtual_offset and
181 image.entry_offset < file_end)
182 {
183 entry_found = true;
184 }
185 previous_source_end = source_end;
186 previous_memory_end = memory_end;
187 }
188 if (!entry_found) return error.ImmutableDescriptorInvalid;
189 for (image.loads[image.load_count..]) |load| {
190 if (!std.meta.eql(load, ImmutableLoad{})) {
191 return error.ImmutableDescriptorInvalid;
192 }
193 }
194 }
195
196 fn immutableCount(execution: manifest.View) u16 {
197 var count: u16 = 0;
198 var index: u16 = 0;
199 while (index < execution.header.load_count) : (index += 1) {
200 if (execution.load(index).flags & manifest.load_flag_write == 0) {
201 count += 1;
202 }
203 }
204 std.debug.assert(count > 0);
205 return count;
206 }
207
208 fn immutableHasher(entry_offset: u32, count: u16) Sha256 {
209 var hasher = Sha256.init(.{});
210 hasher.update(immutable_domain);
211 var header: [8]u8 = @splat(0);
212 std.mem.writeInt(u16, header[0..2], count, .little);
213 std.mem.writeInt(u32, header[4..8], entry_offset, .little);
214 hasher.update(&header);
215 return hasher;
216 }
217
218 fn immutableLoad(load: manifest.Load) ImmutableLoad {
219 return .{
220 .source_offset = load.source_offset,
221 .file_bytes = load.file_bytes,
222 .memory_bytes = load.memory_bytes,
223 .physical_offset = load.physical_offset,
224 .virtual_offset = load.virtual_offset,
225 .flags = load.flags,
226 };
227 }
228
229 fn hashLoad(hasher: *Sha256, load: ImmutableLoad) void {
230 var encoded: [os.boot.kernel.load_range_bytes]u8 = undefined;
231 os.boot.kernel.encodeLoadRange(.{
232 .source_offset = load.source_offset,
233 .file_bytes = load.file_bytes,
234 .memory_bytes = load.memory_bytes,
235 .physical_offset = load.physical_offset,
236 .virtual_offset = load.virtual_offset,
237 .flags = load.flags,
238 .reserved = 0,
239 }, &encoded);
240 hasher.update(&encoded);
241 }
242
243 fn finishDigest(hasher: *Sha256) manifest.Digest {
244 var digest: manifest.Digest = undefined;
245 hasher.final(&digest);
246 return digest;
247 }
248
249 comptime {
250 std.debug.assert(manifest.loads_max == 4);
251 }