xref: /xnu-10063.141.1/tests/nvram_tests/nvram_helper.c (revision d8b80295118ef25ac3a784134bcf95cd8e88109f)
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