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 }