lib/sys/src/kvm/test.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const sys = @import("../root.zig");
  3 const kvm = @import("root.zig");
  4 const system_backend = @import("system.zig");
  5 const types = @import("types.zig");
  6 
  7 const abi = kvm.abi;
  8 const Availability = kvm.Availability;
  9 const CapabilityStage = kvm.CapabilityStage;
 10 const Descriptor = types.Descriptor;
 11 const Errno = types.Errno;
 12 const MapResult = types.MapResult;
 13 const RawResult = types.RawResult;
 14 const maximum_interrupt_retries = types.maximum_interrupt_retries;
 15 const page_size = types.page_size;
 16 
 17 test "KVM namespace exposes a no-libc virtualization boundary" {
 18     std.testing.refAllDecls(kvm);
 19     try std.testing.expectEqual(
 20         sys.capabilities.CapabilityTier.no_libc,
 21         kvm.required_capabilities.tier,
 22     );
 23     try std.testing.expect(kvm.required_capabilities.requires(.descriptors));
 24     try std.testing.expect(kvm.required_capabilities.requires(.memory_mapping));
 25     try std.testing.expect(kvm.required_capabilities.requires(.virtualization));
 26     try std.testing.expectEqual(@as(usize, 1), kvm.System.descriptor_capacity);
 27     try std.testing.expectEqual(@as(usize, 1), kvm.Vm.descriptor_capacity);
 28     try std.testing.expectEqual(@as(usize, 1), kvm.Vcpu.descriptor_capacity);
 29     try std.testing.expectEqual(@as(usize, 1), kvm.Vcpu.mapping_capacity);
 30 }
 31 
 32 test "KVM host probe never substitutes an unavailable backend" {
 33     const receipt = kvm.probe();
 34     if (!kvm.supported) {
 35         try std.testing.expectEqual(kvm.Availability.unsupported, receipt.availability);
 36         try std.testing.expectEqual(kvm.CapabilityStage.platform, receipt.stage);
 37         return;
 38     }
 39     if (receipt.availability == .available) {
 40         try std.testing.expectEqual(kvm.CapabilityStage.complete, receipt.stage);
 41         try std.testing.expectEqual(kvm.abi.api_version, receipt.api_version);
 42         try std.testing.expect(receipt.extensions.user_memory != 0);
 43     } else {
 44         try std.testing.expect(receipt.stage != .complete);
 45     }
 46 }
 47 
 48 const TestAction = enum {
 49     open,
 50     control,
 51     map,
 52     unmap,
 53     close,
 54 };
 55 
 56 const TestRecord = struct {
 57     action: TestAction,
 58     descriptor: Descriptor = -1,
 59     request: u32 = 0,
 60     argument: usize = 0,
 61 };
 62 
 63 const FakeOperations = struct {
 64     platform_available: bool = true,
 65     open_failure: ?Errno = null,
 66     control_failure_request: ?u32 = null,
 67     control_failure: Errno = .IO,
 68     map_failure: ?Errno = null,
 69     api: u32 = abi.api_version,
 70     run_bytes: u32 = 4096,
 71     user_memory: u32 = 1,
 72     records: [192]TestRecord = undefined,
 73     record_count: usize = 0,
 74     registered_region: ?abi.UserMemoryRegion = null,
 75     run_storage: [4096]u8 align(page_size) = @splat(0),
 76 
 77     pub fn platformSupported(self: *@This()) bool {
 78         return self.platform_available;
 79     }
 80 
 81     pub fn openDevice(self: *@This()) RawResult {
 82         self.record(.{ .action = .open });
 83         if (self.open_failure) |code| return .{ .failure = code };
 84         return .{ .value = 10 };
 85     }
 86 
 87     pub fn control(
 88         self: *@This(),
 89         descriptor: Descriptor,
 90         request: u32,
 91         argument: usize,
 92     ) RawResult {
 93         self.record(.{
 94             .action = .control,
 95             .descriptor = descriptor,
 96             .request = request,
 97             .argument = argument,
 98         });
 99         if (self.control_failure_request == request) {
100             return .{ .failure = self.control_failure };
101         }
102         return self.controlResult(request, argument);
103     }
104 
105     pub fn mapRun(self: *@This(), descriptor: Descriptor, len: usize) MapResult {
106         self.record(.{ .action = .map, .descriptor = descriptor, .argument = len });
107         if (self.map_failure) |code| return .{ .failure = code };
108         if (len > self.run_storage.len) return .{ .failure = .NOMEM };
109         return .{ .mapping = self.run_storage[0..len] };
110     }
111 
112     pub fn unmap(self: *@This(), mapping: []align(page_size) u8) void {
113         std.debug.assert(mapping.ptr == self.run_storage[0..].ptr);
114         self.record(.{ .action = .unmap, .argument = mapping.len });
115     }
116 
117     pub fn close(self: *@This(), descriptor: Descriptor) void {
118         self.record(.{ .action = .close, .descriptor = descriptor });
119     }
120 
121     fn controlResult(self: *@This(), request: u32, argument: usize) RawResult {
122         if (request == abi.request.get_api_version) return .{ .value = self.api };
123         if (request == abi.request.get_vcpu_mmap_size) return .{ .value = self.run_bytes };
124         if (request == abi.request.check_extension) {
125             return .{ .value = self.extensionValue(argument) };
126         }
127         if (request == abi.request.create_vm) return .{ .value = 20 };
128         if (request == abi.request.create_vcpu) return .{ .value = 30 };
129         if (request == abi.request.set_user_memory_region) {
130             const region: *const abi.UserMemoryRegion = @ptrFromInt(argument);
131             self.registered_region = region.*;
132             return .{ .value = 0 };
133         }
134         if (request == abi.request.run) {
135             const header: *abi.RunHeader = @ptrCast(&self.run_storage);
136             header.exit_reason = @backingInt(kvm.ExitReason.halted);
137             return .{ .value = 0 };
138         }
139         return self.stateResult(request, argument);
140     }
141 
142     fn extensionValue(self: *@This(), raw_extension: usize) usize {
143         const extension_value: u32 = @intCast(raw_extension);
144         const extension: abi.Extension = @fromBackingInt(extension_value);
145         return switch (extension) {
146             .user_memory => self.user_memory,
147             .recommended_vcpus => 2,
148             .memory_slots => 32,
149             .maximum_vcpus => 4,
150             .maximum_vcpu_id => 16,
151             .xsave_2 => @sizeOf(abi.Xsave),
152             else => 1,
153         };
154     }
155 
156     fn stateResult(self: *@This(), request: u32, argument: usize) RawResult {
157         if (request == abi.request.get_registers) {
158             clear(abi.Registers, argument).rip = 0x1234;
159         } else if (request == abi.request.get_special_registers) {
160             _ = clear(abi.SpecialRegisters, argument);
161         } else if (request == abi.request.get_fpu) {
162             _ = clear(abi.Fpu, argument);
163         } else if (request == abi.request.get_vcpu_events) {
164             _ = clear(abi.VcpuEvents, argument);
165         } else if (request == abi.request.get_debug_registers) {
166             _ = clear(abi.DebugRegisters, argument);
167         } else if (request == abi.request.get_xsave) {
168             _ = clear(abi.Xsave, argument);
169         } else if (request == abi.request.get_xcrs) {
170             _ = clear(abi.Xcrs, argument);
171         } else if (request == abi.request.get_mp_state) {
172             _ = clear(abi.MpState, argument);
173         } else if (request == abi.request.get_clock) {
174             clear(abi.Clock, argument).clock = 99;
175         } else if (request == abi.request.get_msrs) {
176             return getMsrs(argument);
177         } else if (request == abi.request.set_msrs) {
178             return msrCount(argument);
179         } else if (request == abi.request.get_supported_cpuid) {
180             return self.supportedCpuid(argument);
181         } else if (isStateWrite(request) or isCpuid(request)) {
182             return .{ .value = 0 };
183         } else {
184             return .{ .failure = .NOTTY };
185         }
186         return .{ .value = 0 };
187     }
188 
189     fn supportedCpuid(_: *const @This(), address: usize) RawResult {
190         const header: *abi.CpuidHeader = @ptrFromInt(address);
191         if (header.count == 0) return .{ .failure = .@"2BIG" };
192         const entries: [*]abi.CpuidEntry = @ptrFromInt(address + @sizeOf(abi.CpuidHeader));
193         header.count = 1;
194         entries[0] = std.mem.zeroes(abi.CpuidEntry);
195         entries[0].function = 0x8000_0001;
196         entries[0].edx = @as(u32, 1) << 20;
197         return .{ .value = 0 };
198     }
199 
200     fn record(self: *@This(), value: TestRecord) void {
201         std.debug.assert(self.record_count < self.records.len);
202         self.records[self.record_count] = value;
203         self.record_count += 1;
204     }
205 
206     fn actionCount(self: *const @This(), action: TestAction) usize {
207         var count: usize = 0;
208         for (self.records[0..self.record_count]) |entry| {
209             if (entry.action == action) count += 1;
210         }
211         return count;
212     }
213 
214     fn descriptorCloseCount(self: *const @This(), descriptor: Descriptor) usize {
215         var count: usize = 0;
216         for (self.records[0..self.record_count]) |entry| {
217             if (entry.action == .close and entry.descriptor == descriptor) count += 1;
218         }
219         return count;
220     }
221 
222     fn clear(comptime T: type, address: usize) *T {
223         const destination: *T = @ptrFromInt(address);
224         destination.* = std.mem.zeroes(T);
225         return destination;
226     }
227 
228     fn getMsrs(address: usize) RawResult {
229         const header: *abi.MsrHeader = @ptrFromInt(address);
230         std.debug.assert(header.count <= 16);
231         const entries: [*]abi.MsrEntry = @ptrFromInt(address + @sizeOf(abi.MsrHeader));
232         for (entries[0..header.count], 0..) |*entry, index| {
233             entry.data = 0x1000 + index;
234         }
235         return .{ .value = header.count };
236     }
237 
238     fn msrCount(address: usize) RawResult {
239         const header: *const abi.MsrHeader = @ptrFromInt(address);
240         return .{ .value = header.count };
241     }
242 
243     fn isStateWrite(request: u32) bool {
244         return request == abi.request.set_registers or
245             request == abi.request.set_special_registers or
246             request == abi.request.set_fpu or
247             request == abi.request.set_vcpu_events or
248             request == abi.request.set_debug_registers or
249             request == abi.request.set_xsave or
250             request == abi.request.set_xcrs or
251             request == abi.request.set_mp_state or
252             request == abi.request.set_clock;
253     }
254 
255     fn isCpuid(request: u32) bool {
256         return request == abi.request.get_supported_cpuid or
257             request == abi.request.get_cpuid or
258             request == abi.request.set_cpuid;
259     }
260 };
261 
262 fn readySystem(operations: *FakeOperations) !system_backend.System {
263     return switch (system_backend.acquire(types.operationsFor(operations))) {
264         .ready => |system| system,
265         .rejected => error.UnexpectedRejection,
266     };
267 }
268 
269 fn expectDeviceRejection(code: Errno, availability: Availability) !void {
270     var operations: FakeOperations = .{ .open_failure = code };
271     const attempt = system_backend.acquire(types.operationsFor(&operations));
272     switch (attempt) {
273         .ready => return error.UnexpectedAvailability,
274         .rejected => |receipt| {
275             try std.testing.expectEqual(availability, receipt.availability);
276             try std.testing.expectEqual(CapabilityStage.device, receipt.stage);
277             try std.testing.expectEqual(
278                 @as(u16, @intCast(@backingInt(code))),
279                 receipt.errno_code,
280             );
281         },
282     }
283     try std.testing.expectEqual(@as(usize, 0), operations.actionCount(.close));
284     const expected_opens: usize = if (code == .INTR) maximum_interrupt_retries else 1;
285     try std.testing.expectEqual(expected_opens, operations.actionCount(.open));
286 }
287 
288 test "KVM capability receipt classifies device rejection" {
289     try expectDeviceRejection(.NOENT, .unavailable);
290     try expectDeviceRejection(.NODEV, .unavailable);
291     try expectDeviceRejection(.ACCES, .denied);
292     try expectDeviceRejection(.NOSYS, .unsupported);
293     try expectDeviceRejection(.IO, .failed);
294     try expectDeviceRejection(.INTR, .failed);
295 
296     var operations: FakeOperations = .{ .platform_available = false };
297     const attempt = system_backend.acquire(types.operationsFor(&operations));
298     try std.testing.expectEqual(
299         Availability.unsupported,
300         attempt.rejected.availability,
301     );
302     try std.testing.expectEqual(CapabilityStage.platform, attempt.rejected.stage);
303     try std.testing.expectEqual(@as(usize, 0), operations.actionCount(.open));
304 }
305 
306 test "KVM capability receipt closes rejected system descriptor" {
307     var incompatible: FakeOperations = .{ .api = abi.api_version + 1 };
308     const version_attempt = system_backend.acquire(types.operationsFor(&incompatible));
309     try std.testing.expectEqual(
310         Availability.unsupported,
311         version_attempt.rejected.availability,
312     );
313     try std.testing.expectEqual(CapabilityStage.api_version, version_attempt.rejected.stage);
314     try std.testing.expectEqual(@as(usize, 1), incompatible.descriptorCloseCount(10));
315 
316     var missing_memory: FakeOperations = .{ .user_memory = 0 };
317     const memory_attempt = system_backend.acquire(types.operationsFor(&missing_memory));
318     try std.testing.expectEqual(
319         Availability.unsupported,
320         memory_attempt.rejected.availability,
321     );
322     try std.testing.expectEqual(CapabilityStage.extensions, memory_attempt.rejected.stage);
323     try std.testing.expectEqual(@as(usize, 1), missing_memory.descriptorCloseCount(10));
324 
325     var invalid_mapping: FakeOperations = .{ .run_bytes = 3000 };
326     const mapping_attempt = system_backend.acquire(types.operationsFor(&invalid_mapping));
327     try std.testing.expectEqual(
328         Availability.unsupported,
329         mapping_attempt.rejected.availability,
330     );
331     try std.testing.expectEqual(CapabilityStage.run_mapping, mapping_attempt.rejected.stage);
332     try std.testing.expectEqual(@as(usize, 1), invalid_mapping.descriptorCloseCount(10));
333 
334     var denied_extension: FakeOperations = .{
335         .control_failure_request = abi.request.check_extension,
336         .control_failure = .ACCES,
337     };
338     const extension_attempt = system_backend.acquire(types.operationsFor(&denied_extension));
339     try std.testing.expectEqual(Availability.denied, extension_attempt.rejected.availability);
340     try std.testing.expectEqual(CapabilityStage.extensions, extension_attempt.rejected.stage);
341     try std.testing.expectEqual(@as(usize, 1), denied_extension.descriptorCloseCount(10));
342 }
343 
344 test "KVM system acquisition returns measured capabilities and closes once" {
345     var operations: FakeOperations = .{};
346     var system = try readySystem(&operations);
347     try std.testing.expectEqual(Availability.available, system.receipt.availability);
348     try std.testing.expectEqual(CapabilityStage.complete, system.receipt.stage);
349     try std.testing.expectEqual(abi.api_version, system.receipt.api_version);
350     try std.testing.expectEqual(@as(u32, 4096), system.receipt.run_mapping_bytes);
351     try std.testing.expectEqual(@as(u32, 32), system.receipt.extensions.memory_slots);
352     try std.testing.expectEqual(@as(u32, 4), system.receipt.extensions.maximum_vcpus);
353     try std.testing.expectEqual(@as(u32, 32), system.memorySlotCapacity());
354     try std.testing.expect(system.supportsReadOnlyMemory());
355 
356     system.deinit();
357     system.deinit();
358     try std.testing.expectEqual(@as(usize, 1), operations.descriptorCloseCount(10));
359 }
360 
361 test "KVM owners route memory run and state through typed controls" {
362     var operations: FakeOperations = .{};
363     var system = try readySystem(&operations);
364     defer system.deinit();
365     var vm = try system.createVm(0);
366     defer vm.deinit();
367 
368     var guest: [page_size]u8 align(page_size) = @splat(0);
369     try vm.registerMemory(.{
370         .slot = 3,
371         .guest_physical_address = 0x4000,
372         .memory = guest[0..],
373         .flags = .{ .read_only = true },
374     });
375     try std.testing.expectEqual(@as(u32, 3), operations.registered_region.?.slot);
376     const registered = operations.registered_region.?;
377     try std.testing.expectEqual(@as(u64, 0x4000), registered.guest_physical_address);
378     try std.testing.expectEqual(@as(u64, page_size), registered.memory_size);
379     try std.testing.expectEqual(@intFromPtr(&guest), registered.userspace_address);
380     try vm.unregisterMemory(3);
381     try std.testing.expectEqual(@as(u64, 0), operations.registered_region.?.memory_size);
382 
383     var vcpu = try vm.createVcpu(0);
384     defer vcpu.deinit();
385     try vcpu.setRunControls(.{
386         .request_interrupt_window = true,
387         .cr8 = 3,
388         .apic_base = 0xfee00000,
389     });
390     try vcpu.setImmediateExit(false);
391     const before_run = try vcpu.runStatus();
392     try std.testing.expectEqual(@as(u64, 3), before_run.cr8);
393     try std.testing.expectEqual(@as(u64, 0xfee00000), before_run.apic_base);
394     const run_exit = try vcpu.run();
395     try std.testing.expectEqual(.halted, std.meta.activeTag(run_exit));
396 
397     var snapshot = try vcpu.capture(.all);
398     try std.testing.expectEqual(@as(u64, 0x1234), snapshot.registers.rip);
399     try vcpu.restore(&snapshot);
400 
401     const Msrs = abi.MsrBuffer(2);
402     var msrs = try Msrs.init(&.{ 0x10, 0x1b });
403     try vcpu.readMsrs(&msrs);
404     try std.testing.expectEqual(@as(u64, 0x1000), msrs.entries[0].data);
405     try vcpu.writeMsrs(&msrs);
406 
407     const Cpuids = abi.CpuidBuffer(4);
408     var cpuids = Cpuids.init();
409     try system.supportedCpuid(&cpuids);
410     try vcpu.readCpuid(&cpuids);
411     try vcpu.writeCpuid(&cpuids);
412 
413     const clock = try vm.getClock();
414     try std.testing.expectEqual(@as(u64, 99), clock.clock);
415     try vm.setClock(&clock);
416 
417     vcpu.deinit();
418     vm.deinit();
419     system.deinit();
420     const tail = operations.records[operations.record_count - 4 .. operations.record_count];
421     try std.testing.expectEqual(TestAction.unmap, tail[0].action);
422     try std.testing.expectEqual(TestAction.close, tail[1].action);
423     try std.testing.expectEqual(@as(Descriptor, 30), tail[1].descriptor);
424     try std.testing.expectEqual(@as(Descriptor, 20), tail[2].descriptor);
425     try std.testing.expectEqual(@as(Descriptor, 10), tail[3].descriptor);
426 }
427 
428 test "KVM vcpu map failure closes only the new descriptor" {
429     var operations: FakeOperations = .{ .map_failure = .NOMEM };
430     var system = try readySystem(&operations);
431     defer system.deinit();
432     var vm = try system.createVm(0);
433     defer vm.deinit();
434 
435     try std.testing.expectError(error.ResourceLimit, vm.createVcpu(0));
436     try std.testing.expectEqual(@as(usize, 1), operations.descriptorCloseCount(30));
437     try std.testing.expectEqual(@as(usize, 0), operations.actionCount(.unmap));
438     try std.testing.expectEqual(@as(usize, 0), operations.descriptorCloseCount(20));
439     try std.testing.expectEqual(@as(usize, 0), operations.descriptorCloseCount(10));
440 }
441 
442 test "KVM owners reject capacity violations before kernel control" {
443     var operations: FakeOperations = .{};
444     var system = try readySystem(&operations);
445     defer system.deinit();
446     var vm = try system.createVm(0);
447     defer vm.deinit();
448     const controls_before = operations.actionCount(.control);
449 
450     var guest: [page_size]u8 align(page_size) = @splat(0);
451     try std.testing.expectError(error.InvalidSlot, vm.registerMemory(.{
452         .slot = vm.extensions.memory_slots,
453         .guest_physical_address = 0,
454         .memory = guest[0..],
455     }));
456     vm.extensions.read_only_memory = 0;
457     try std.testing.expectError(error.UnsupportedFlags, vm.registerMemory(.{
458         .slot = 0,
459         .guest_physical_address = 0,
460         .memory = guest[0..],
461         .flags = .{ .read_only = true },
462     }));
463     try std.testing.expectError(error.InvalidAlignment, vm.registerMemory(.{
464         .slot = 0,
465         .guest_physical_address = 1,
466         .memory = guest[0..],
467     }));
468     try std.testing.expectError(
469         error.UnsupportedOperation,
470         vm.createVcpu(vm.extensions.maximum_vcpu_id),
471     );
472     try std.testing.expectEqual(controls_before, operations.actionCount(.control));
473 }
474 
475 test "KVM state buffers and run interruption fail before unsafe reuse" {
476     var operations: FakeOperations = .{};
477     var system = try readySystem(&operations);
478     defer system.deinit();
479     var vm = try system.createVm(0);
480     defer vm.deinit();
481     var vcpu = try vm.createVcpu(0);
482     defer vcpu.deinit();
483 
484     const Msrs = abi.MsrBuffer(1);
485     var msrs = try Msrs.init(&.{0x10});
486     msrs.count = 2;
487     try std.testing.expectError(error.CapacityExceeded, vcpu.readMsrs(&msrs));
488     vcpu.extensions.xsave = 0;
489     try std.testing.expectError(error.UnsupportedState, vcpu.capture(.{ .xsave = true }));
490     vcpu.extensions.immediate_exit = 0;
491     try std.testing.expectError(error.UnsupportedOperation, vcpu.setImmediateExit(true));
492 
493     operations.control_failure_request = abi.request.run;
494     operations.control_failure = .INTR;
495     try std.testing.expectError(error.Interrupted, vcpu.run());
496 }