1/*
2 * Copyright (c) 2021 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29#include <sys/sysctl.h>
30#include <libkern/libkern.h>
31#include <kern/hvg_hypercall.h>
32#include <stdbool.h>
33
34/* Translate hypercall error code to syscall error code */
35static int
36hv_ret_to_errno(hvg_hcall_return_t ret)
37{
38 switch (ret) {
39 case HVG_HCALL_ACCESS_DENIED:
40 return EPERM;
41 case HVG_HCALL_INVALID_CODE:
42 case HVG_HCALL_INVALID_PARAMETER:
43 return EINVAL;
44 case HVG_HCALL_IO_FAILED:
45 return EIO;
46 case HVG_HCALL_FEAT_DISABLED:
47 case HVG_HCALL_UNSUPPORTED:
48 return ENOTSUP;
49 default:
50 return ENODEV;
51 }
52}
53
54/*
55 * Trigger a guest kernel core dump (Intel macOS VM only)
56 * Usage: sysctl kern.hvg.trigger_kernel_coredump = 1
57 * (option selector must be 1, other values reserved).
58 */
59static int
60sysctl_trigger_kernel_coredump(__unused struct sysctl_oid *oidp,
61 __unused void *arg1, __unused int arg2, __unused struct sysctl_req *req)
62{
63 /*
64 * Static because it retains the filename of the last core dump
65 * (returned when the sysctl is read).
66 */
67 static hvg_hcall_vmcore_file_t sysctl_vmcore;
68
69 int error = 0;
70 hvg_hcall_return_t hv_ret;
71 char buf[3]; // 1 digit for dump option + 1 '\0' from userspace sysctl + 1 '\0'
72
73 if (req->newptr) {
74 // Write request
75 // single digit (1 byte) + 1 terminating byte added by system_cmd sysctl
76 if (req->newlen > 2) {
77 return EINVAL;
78 }
79 error = SYSCTL_IN(req, buf, req->newlen);
80 buf[req->newlen] = '\0';
81 if (!error) {
82 if (strcmp(s1: buf, s2: "1") != 0) {
83 return EINVAL;
84 }
85
86 /* Issue hypercall to trigger a dump */
87 hv_ret = hvg_hcall_trigger_dump(vmcore: &sysctl_vmcore, dump_option: HVG_HCALL_DUMP_OPTION_REGULAR);
88 if (hv_ret == HVG_HCALL_SUCCESS) {
89 error = SYSCTL_OUT(req, &sysctl_vmcore, sizeof(sysctl_vmcore));
90 } else {
91 error = hv_ret_to_errno(ret: hv_ret);
92 }
93 }
94 } else {
95 // Read request
96 error = SYSCTL_OUT(req, &sysctl_vmcore, sizeof(sysctl_vmcore));
97 }
98 return error;
99}
100
101/*
102 * Get offset from the host's mach_absolute_time.
103 */
104static int
105sysctl_get_mabs_offset(__unused struct sysctl_oid *oidp, __unused void *arg1,
106 __unused int arg2, struct sysctl_req *req)
107{
108 uint64_t offset = 0;
109
110 const hvg_hcall_return_t ret = hvg_hcall_get_mabs_offset(mabs_offset: &offset);
111 if (ret != HVG_HCALL_SUCCESS) {
112 return hv_ret_to_errno(ret);
113 }
114
115 return SYSCTL_OUT(req, &offset, sizeof(offset));
116}
117
118/*
119 * Get the host's boot session UUID.
120 */
121static int
122sysctl_get_bootsessionuuid(__unused struct sysctl_oid *oidp,
123 __unused void *arg1, __unused int arg2, struct sysctl_req *req)
124{
125 uuid_string_t uuid = {0};
126
127 const hvg_hcall_return_t ret = hvg_hcall_get_bootsessionuuid(uuid);
128 if (ret != HVG_HCALL_SUCCESS) {
129 return hv_ret_to_errno(ret);
130 }
131
132 return SYSCTL_OUT(req, &uuid, sizeof(uuid));
133}
134
135static int
136sysctl_vmm_present(__unused struct sysctl_oid *oidp,
137 __unused void *arg1, __unused int arg2, struct sysctl_req *req)
138{
139 int vmm_present = 0;
140
141#if defined(__arm64__)
142 extern int IODTGetDefault(const char *key, void *infoAddr, unsigned int infoSize );
143 (void) IODTGetDefault(key: "vmm-present", infoAddr: &vmm_present, infoSize: sizeof(vmm_present));
144#elif defined(__x86_64__)
145 extern boolean_t cpuid_vmm_present(void);
146 vmm_present = cpuid_vmm_present();
147#endif
148
149 vmm_present = !!vmm_present;
150 return SYSCTL_OUT(req, &vmm_present, sizeof(vmm_present));
151}
152
153static SYSCTL_NODE(_kern, OID_AUTO, hvg,
154 CTLFLAG_RW | CTLFLAG_LOCKED | CTLFLAG_ANYBODY, 0,
155 "hypervisor guest");
156
157static SYSCTL_PROC(_kern_hvg, OID_AUTO, trigger_kernel_coredump,
158 CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_NOAUTO,
159 NULL, 0,
160 sysctl_trigger_kernel_coredump, "A",
161 "Request that the hypervisor take a live kernel dump");
162
163static SYSCTL_PROC(_kern_hvg, OID_AUTO, mabs_offset,
164 CTLTYPE_QUAD | CTLFLAG_RD | CTLFLAG_NOAUTO | CTLFLAG_LOCKED,
165 NULL, 0,
166 sysctl_get_mabs_offset, "Q",
167 "host time offset");
168
169static SYSCTL_PROC(_kern_hvg, OID_AUTO, host_bootsessionuuid,
170 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_NOAUTO | CTLFLAG_LOCKED,
171 NULL, 0,
172 sysctl_get_bootsessionuuid, "A",
173 "host boot session UUID");
174
175/* Not dynamic, no need to manually register. */
176static SYSCTL_PROC(_kern, OID_AUTO, hv_vmm_present,
177 CTLTYPE_INT | CTLFLAG_ANYBODY | CTLFLAG_KERN | CTLFLAG_LOCKED,
178 NULL, 0,
179 sysctl_vmm_present, "I",
180 "running on a vmm");
181
182__startup_func
183static void
184hvg_sysctl_init(void)
185{
186 struct {
187 hvg_hcall_code_t hcall;
188 struct sysctl_oid *oid;
189 } hvg_sysctl[] = {
190 {
191 .hcall = HVG_HCALL_TRIGGER_DUMP,
192 .oid = &sysctl__kern_hvg_trigger_kernel_coredump,
193 },
194 {
195 .hcall = HVG_HCALL_GET_MABS_OFFSET,
196 .oid = &sysctl__kern_hvg_mabs_offset,
197 },
198 {
199 .hcall = HVG_HCALL_GET_BOOTSESSIONUUID,
200 .oid = &sysctl__kern_hvg_host_bootsessionuuid,
201 },
202 };
203
204#define countof(x) (sizeof(x) / sizeof(x[0]))
205 for (int i = 0; i < countof(hvg_sysctl); i++) {
206 if (hvg_is_hcall_available(hvg_sysctl[i].hcall)) {
207 sysctl_register_oid_early(oidp: hvg_sysctl[i].oid);
208 }
209 }
210}
211
212STARTUP(SYSCTL, STARTUP_RANK_MIDDLE, hvg_sysctl_init);
213