1 #include <darwintest.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include "nvram_helper.h"
6
7 // Ascii value of 'A' (65) - Ascii value of '9' (57)
8 #define ASCII_OFFSET 7
9 #define NVRAM_BYTE_LEN 3
10
11 // NVRAM helper functions from https://stashweb.sd.apple.com/projects/COREOS/repos/system_cmds/browse/nvram.tproj/nvram.c
12
13 /**
14 * @brief Print the given firmware variable.
15 */
16 static void
PrintVariable(const void * key,const void * value)17 PrintVariable(const void *key, const void *value)
18 {
19 if (CFGetTypeID(key) != CFStringGetTypeID()) {
20 printf("Variable name passed in isn't a string");
21 return;
22 }
23 long cnt, cnt2;
24 CFIndex nameLen;
25 char *nameBuffer = 0;
26 const char *nameString;
27 char numberBuffer[10];
28 const uint8_t *dataPtr;
29 uint8_t dataChar;
30 char *dataBuffer = 0;
31 CFIndex valueLen;
32 char *valueBuffer = 0;
33 const char *valueString = 0;
34 uint32_t number;
35 long length;
36 CFTypeID typeID;
37 // Get the variable's name.
38 nameLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(key), kCFStringEncodingUTF8) + 1;
39
40 nameBuffer = malloc(nameLen);
41
42 if (nameBuffer && CFStringGetCString(key, nameBuffer, nameLen, kCFStringEncodingUTF8)) {
43 nameString = nameBuffer;
44 } else {
45 printf("Unable to convert property name to C string");
46 nameString = "<UNPRINTABLE>";
47 }
48
49 // Get the variable's type.
50 typeID = CFGetTypeID(value);
51
52 if (typeID == CFBooleanGetTypeID()) {
53 if (CFBooleanGetValue(value)) {
54 valueString = "true";
55 } else {
56 valueString = "false";
57 }
58 } else if (typeID == CFNumberGetTypeID()) {
59 CFNumberGetValue(value, kCFNumberSInt32Type, &number);
60 if (number == 0xFFFFFFFF) {
61 sprintf(numberBuffer, "-1");
62 } else {
63 sprintf(numberBuffer, "0x%x", number);
64 }
65 valueString = numberBuffer;
66 } else if (typeID == CFStringGetTypeID()) {
67 valueLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(value),
68 kCFStringEncodingUTF8) +
69 1;
70 valueBuffer = malloc(valueLen + 1);
71 if (valueBuffer && CFStringGetCString(value, valueBuffer, valueLen, kCFStringEncodingUTF8)) {
72 valueString = valueBuffer;
73 } else {
74 printf("Unable to convert value to C string");
75 valueString = "<UNPRINTABLE>";
76 }
77 } else if (typeID == CFDataGetTypeID()) {
78 length = CFDataGetLength(value);
79 if (length == 0) {
80 valueString = "";
81 } else {
82 dataBuffer = malloc(length * NVRAM_BYTE_LEN + NVRAM_BYTE_LEN);
83 if (dataBuffer != 0) {
84 dataPtr = CFDataGetBytePtr(value);
85 cnt = cnt2 = 0;
86 for (; cnt < length; cnt++) {
87 dataChar = dataPtr[cnt];
88 if (isprint(dataChar) && dataChar != '%') {
89 dataBuffer[cnt2++] = dataChar;
90 } else {
91 sprintf(dataBuffer + cnt2, "%%%02x", dataChar);
92 cnt2 += NVRAM_BYTE_LEN;
93 }
94 }
95 dataBuffer[cnt2] = '\0';
96 valueString = dataBuffer;
97 }
98 }
99 } else {
100 valueString = "<INVALID>";
101 }
102
103 if ((nameString != 0) && (valueString != 0)) {
104 printf("%s\t%s\n", nameString, valueString);
105 }
106
107 if (dataBuffer != 0) {
108 free(dataBuffer);
109 }
110 if (nameBuffer != 0) {
111 free(nameBuffer);
112 }
113 if (valueBuffer != 0) {
114 free(valueBuffer);
115 }
116 }
117
118 /**
119 * @brief Convert the value into a CFType given the typeID
120 */
121 static CFTypeRef
ConvertValueToCFTypeRef(CFTypeID typeID,const char * value)122 ConvertValueToCFTypeRef(CFTypeID typeID, const char *value)
123 {
124 CFTypeRef valueRef = 0;
125 long cnt, cnt2, length;
126 unsigned long number, tmp;
127
128 if (typeID == CFBooleanGetTypeID()) {
129 if (value == NULL) {
130 return valueRef;
131 }
132 if (!strcmp("true", value)) {
133 valueRef = kCFBooleanTrue;
134 } else if (!strcmp("false", value)) {
135 valueRef = kCFBooleanFalse;
136 }
137 } else if (typeID == CFNumberGetTypeID()) {
138 number = strtol(value, 0, 0);
139 valueRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
140 &number);
141 } else if (typeID == CFStringGetTypeID()) {
142 valueRef = CFStringCreateWithCString(kCFAllocatorDefault, value,
143 kCFStringEncodingUTF8);
144 } else if (typeID == CFDataGetTypeID()) {
145 if (value == NULL) {
146 length = 0;
147 } else {
148 length = strlen(value);
149 }
150
151 char valueCopy[length + 1];
152
153 for (cnt = cnt2 = 0; cnt < length; cnt++, cnt2++) {
154 if (value[cnt] == '%') {
155 if ((cnt + 2 > length) ||
156 !ishexnumber(value[cnt + 1]) ||
157 !ishexnumber(value[cnt + 2])) {
158 return 0;
159 }
160 number = toupper(value[++cnt]) - '0';
161 if (number > 9) {
162 number -= ASCII_OFFSET;
163 }
164 tmp = toupper(value[++cnt]) - '0';
165 if (tmp > 9) {
166 tmp -= ASCII_OFFSET;
167 }
168 number = (number << 4) + tmp;
169 valueCopy[cnt2] = number;
170 } else {
171 valueCopy[cnt2] = value[cnt];
172 }
173 }
174 valueRef = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)valueCopy, cnt2);
175 } else {
176 return 0;
177 }
178
179 return valueRef;
180 }
181
182 /**
183 * @brief Prints the variable. returns kIOReturnNotFound if not found
184 */
185 static kern_return_t
GetVariable(const char * name,io_registry_entry_t optionsRef)186 GetVariable(const char *name, io_registry_entry_t optionsRef)
187 {
188 CFStringRef nameRef = NULL;
189 CFTypeRef valueRef = NULL;
190 nameRef = CFStringCreateWithCString(kCFAllocatorDefault, name,
191 kCFStringEncodingUTF8);
192 if (nameRef == NULL) {
193 printf("Error creating CFString for key %s", name);
194 return KERN_FAILURE;
195 }
196
197 valueRef = IORegistryEntryCreateCFProperty(optionsRef, nameRef, 0, 0);
198 if (valueRef == NULL) {
199 CFRelease(nameRef);
200 return kIOReturnNotFound;
201 }
202
203 PrintVariable(nameRef, valueRef);
204
205 CFRelease(nameRef);
206 CFRelease(valueRef);
207
208 return KERN_SUCCESS;
209 }
210
211 /**
212 * @brief Returns variable type. 0xFF if variable doesn't exist or on error creating CFString
213 */
214 CFTypeID
GetVarType(const char * name,io_registry_entry_t optionsRef)215 GetVarType(const char *name, io_registry_entry_t optionsRef)
216 {
217 CFStringRef nameRef = NULL;
218 CFTypeRef valueRef = NULL;
219 CFTypeID typeID = 0;
220
221 nameRef = CFStringCreateWithCString(kCFAllocatorDefault, name,
222 kCFStringEncodingUTF8);
223 if (nameRef != NULL) {
224 valueRef = IORegistryEntryCreateCFProperty(optionsRef, nameRef, 0, 0);
225 CFRelease(nameRef);
226 if (valueRef != NULL) {
227 typeID = CFGetTypeID(valueRef);
228 CFRelease(valueRef);
229 }
230 }
231
232 return typeID;
233 }
234
235 /**
236 * @brief Set the named variable with the value passed in
237 */
238 static kern_return_t
SetVariable(const char * name,const char * value,io_registry_entry_t optionsRef)239 SetVariable(const char *name, const char *value, io_registry_entry_t optionsRef)
240 {
241 CFStringRef nameRef;
242 CFTypeRef valueRef;
243 CFTypeID typeID;
244 kern_return_t result = KERN_FAILURE;
245
246 nameRef = CFStringCreateWithCString(kCFAllocatorDefault, name,
247 kCFStringEncodingUTF8);
248 if (nameRef == 0) {
249 printf("Error creating CFString for key %s", name);
250 return result;
251 }
252
253 valueRef = IORegistryEntryCreateCFProperty(optionsRef, nameRef, 0, 0);
254 if (valueRef) {
255 typeID = CFGetTypeID(valueRef);
256 CFRelease(valueRef);
257 valueRef = ConvertValueToCFTypeRef(typeID, value);
258 if (valueRef == 0) {
259 printf("Error creating CFTypeRef for value %s", value);
260 return result;
261 }
262 result = IORegistryEntrySetCFProperty(optionsRef, nameRef, valueRef);
263 } else {
264 // if it's one of the delete/sync keys, it has to be an OSString since the "value" will be the variable name.
265 if (strncmp(name, kIONVRAMDeletePropertyKey, strlen(kIONVRAMDeletePropertyKey)) == 0 ||
266 strncmp(name, kIONVRAMDeletePropertyKeyWRet, strlen(kIONVRAMDeletePropertyKeyWRet)) == 0 ||
267 strncmp(name, kIONVRAMSyncNowPropertyKey, strlen(kIONVRAMSyncNowPropertyKey)) == 0) {
268 valueRef = ConvertValueToCFTypeRef(CFStringGetTypeID(), value);
269 if (valueRef != 0) {
270 result = IORegistryEntrySetCFProperty(optionsRef, nameRef, valueRef);
271 }
272 } else {
273 // In the default case, try data, string, number, then boolean.
274 CFTypeID types[] = {CFDataGetTypeID(),
275 CFStringGetTypeID(), CFNumberGetTypeID(), CFBooleanGetTypeID()};
276 for (unsigned long i = 0; i < sizeof(types) / sizeof(types[0]); i++) {
277 valueRef = ConvertValueToCFTypeRef(types[i], value);
278 if (valueRef != 0) {
279 result = IORegistryEntrySetCFProperty(optionsRef, nameRef, valueRef);
280 if (result == KERN_SUCCESS || result == kIOReturnNoMemory || result == kIOReturnNoSpace) {
281 break;
282 }
283 }
284 }
285 }
286 }
287
288 CFRelease(nameRef);
289
290 return result;
291 }
292
293 /**
294 * @brief Delete named variable
295 */
296 static kern_return_t
DeleteVariable(const char * name,io_registry_entry_t optionsRef)297 DeleteVariable(const char *name, io_registry_entry_t optionsRef)
298 {
299 // Since delete always returns ok, read to make sure it is deleted.
300 if (SetVariable(kIONVRAMDeletePropertyKey, name, optionsRef) == KERN_SUCCESS) {
301 if (GetVariable(name, optionsRef) == kIOReturnNotFound) {
302 return KERN_SUCCESS;
303 }
304 }
305 return KERN_FAILURE;
306 }
307
308 /**
309 * @brief Delete named variable with return code
310 */
311 static kern_return_t
DeleteVariableWRet(const char * name,io_registry_entry_t optionsRef)312 DeleteVariableWRet(const char *name, io_registry_entry_t optionsRef)
313 {
314 return SetVariable(kIONVRAMDeletePropertyKeyWRet, name, optionsRef);
315 }
316
317 /**
318 * @brief Sync to nvram store
319 */
320 static kern_return_t
SyncNVRAM(const char * name,io_registry_entry_t optionsRef)321 SyncNVRAM(const char *name, io_registry_entry_t optionsRef)
322 {
323 return SetVariable(name, name, optionsRef);
324 }
325
326 /**
327 * @brief Get the Options object
328 */
329 io_registry_entry_t
CreateOptionsRef(void)330 CreateOptionsRef(void)
331 {
332 io_registry_entry_t optionsRef = IORegistryEntryFromPath(kIOMainPortDefault, "IODeviceTree:/options");
333 T_ASSERT_NE(optionsRef, IO_OBJECT_NULL, "got options");
334 return optionsRef;
335 }
336
337 /**
338 * @brief Release option object passed in
339 */
340 void
ReleaseOptionsRef(io_registry_entry_t optionsRef)341 ReleaseOptionsRef(io_registry_entry_t optionsRef)
342 {
343 if (optionsRef != IO_OBJECT_NULL) {
344 IOObjectRelease(optionsRef);
345 }
346 }
347
348 static const char *
GetOpString(nvram_op op)349 GetOpString(nvram_op op)
350 {
351 switch (op) {
352 case OP_GET:
353 return "read";
354 case OP_SET:
355 return "write";
356 case OP_DEL:
357 case OP_DEL_RET:
358 return "delete";
359 case OP_RES:
360 return "reset";
361 case OP_OBL:
362 return "obliterate";
363 case OP_SYN:
364 return "sync";
365 default:
366 return "unknown";
367 }
368 }
369
370 static const char *
GetRetString(kern_return_t ret)371 GetRetString(kern_return_t ret)
372 {
373 switch (ret) {
374 case KERN_SUCCESS:
375 return "success";
376 case KERN_FAILURE:
377 return "failure";
378 case kIOReturnNotPrivileged:
379 return "not privileged";
380 case kIOReturnError:
381 return "general error";
382 default:
383 return "unknown";
384 }
385 }
386
387 /**
388 * @brief Tests get/set/delete/reset variable
389 */
390 void
TestVarOp(nvram_op op,const char * var,const char * val,kern_return_t exp_ret,io_registry_entry_t optionsRef)391 TestVarOp(nvram_op op, const char *var, const char *val, kern_return_t exp_ret, io_registry_entry_t optionsRef)
392 {
393 kern_return_t ret = KERN_FAILURE;
394
395 if (var == NULL && (op != OP_RES)) {
396 return;
397 }
398
399 switch (op) {
400 case OP_SET:
401 ret = SetVariable(var, val, optionsRef);
402 break;
403 case OP_GET:
404 ret = GetVariable(var, optionsRef);
405 break;
406 case OP_DEL:
407 ret = DeleteVariable(var, optionsRef);
408 break;
409 case OP_DEL_RET:
410 ret = DeleteVariableWRet(var, optionsRef);
411 break;
412 case OP_RES:
413 ret = SetVariable("ResetNVRam", "1", optionsRef);
414 break;
415 case OP_SYN:
416 ret = SyncNVRAM(var, optionsRef);
417 break;
418 case OP_OBL:
419 // Obliterate NVram (system guid deletes all variables in system region, common guid deletes all non-system variables)
420 ret = SetVariable(var, "1", optionsRef);
421 break;
422 default:
423 T_FAIL("TestVarOp: Invalid NVRAM operation %d\n", op);
424 return;
425 }
426
427 // Use kIOReturnInvalid as don't care about return value.
428 if (exp_ret == kIOReturnInvalid) {
429 T_PASS("Operation %s for variable %s returned %s(%#x) but doesn't have an expected return\n", GetOpString(op), var, GetRetString(ret), ret);
430 return;
431 }
432
433 // Allow passing in a value other than KERN_SUCCESS || KERN_FAILURE to assert against
434 // otherwise remain as pass/fail
435 if ((exp_ret == KERN_SUCCESS) || (exp_ret == KERN_FAILURE)) {
436 if (ret != KERN_SUCCESS) {
437 ret = KERN_FAILURE;
438 }
439 }
440
441 T_ASSERT_EQ(ret, exp_ret, "Operation %s for variable %s returned %s(%#x) as expected\n", GetOpString(op), var, GetRetString(ret), ret);
442 }
443