xref: /xnu-10002.61.3/tests/nvram_tests/nvram_helper.c (revision 0f4c859e951fba394238ab619495c4e1d54d0f34)
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 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 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 		// skip testing different CFTypeIDs if there is no entry and it's a delete operation.
265 		if (value == NULL) {
266 			return result;
267 		}
268 		// In the default case, try data, string, number, then boolean.
269 		CFTypeID types[] = {CFDataGetTypeID(),
270 			            CFStringGetTypeID(), CFNumberGetTypeID(), CFBooleanGetTypeID()};
271 		for (unsigned long i = 0; i < sizeof(types) / sizeof(types[0]); i++) {
272 			valueRef = ConvertValueToCFTypeRef(types[i], value);
273 			if (valueRef != 0) {
274 				result = IORegistryEntrySetCFProperty(optionsRef, nameRef, valueRef);
275 				if (result == KERN_SUCCESS || result == kIOReturnNoMemory || result == kIOReturnNoSpace) {
276 					break;
277 				}
278 			}
279 		}
280 	}
281 
282 	CFRelease(nameRef);
283 
284 	return result;
285 }
286 
287 /**
288  * @brief Delete named variable
289  */
290 kern_return_t
DeleteVariable(const char * name,io_registry_entry_t optionsRef)291 DeleteVariable(const char *name, io_registry_entry_t optionsRef)
292 {
293 	// Since delete always returns ok, read to make sure it is deleted.
294 	if (SetVariable(kIONVRAMDeletePropertyKey, name, optionsRef) == KERN_SUCCESS) {
295 		if (GetVariable(name, optionsRef) == kIOReturnNotFound) {
296 			return KERN_SUCCESS;
297 		}
298 	}
299 	return KERN_FAILURE;
300 }
301 
302 /**
303  * @brief Get the Options object
304  */
305 io_registry_entry_t
CreateOptionsRef(void)306 CreateOptionsRef(void)
307 {
308 	io_registry_entry_t optionsRef = IORegistryEntryFromPath(kIOMainPortDefault, "IODeviceTree:/options");
309 	T_ASSERT_NE(optionsRef, IO_OBJECT_NULL, "got options");
310 	return optionsRef;
311 }
312 
313 /**
314  * @brief Release option object passed in
315  */
316 void
ReleaseOptionsRef(io_registry_entry_t optionsRef)317 ReleaseOptionsRef(io_registry_entry_t optionsRef)
318 {
319 	if (optionsRef != IO_OBJECT_NULL) {
320 		IOObjectRelease(optionsRef);
321 	}
322 }
323 
324 static const char *
GetOpString(nvram_op op)325 GetOpString(nvram_op op)
326 {
327 	switch (op) {
328 	case OP_GET:
329 		return "read";
330 	case OP_SET:
331 		return "write";
332 	case OP_DEL:
333 		return "delete";
334 	case OP_RES:
335 		return "reset";
336 	case OP_OBL:
337 		return "obliterate";
338 	default:
339 		return "unknown";
340 	}
341 }
342 
343 static const char *
GetRetString(kern_return_t ret)344 GetRetString(kern_return_t ret)
345 {
346 	switch (ret) {
347 	case KERN_SUCCESS:
348 		return "success";
349 	case KERN_FAILURE:
350 		return "failure";
351 	case kIOReturnNotPrivileged:
352 		return "not privileged";
353 	default:
354 		return "unknown";
355 	}
356 }
357 
358 /**
359  * @brief Tests get/set/delete/reset variable
360  */
361 void
TestVarOp(nvram_op op,const char * var,const char * val,kern_return_t exp_ret,io_registry_entry_t optionsRef)362 TestVarOp(nvram_op op, const char *var, const char *val, kern_return_t exp_ret, io_registry_entry_t optionsRef)
363 {
364 	kern_return_t ret = KERN_FAILURE;
365 
366 	if (var == NULL && (op != OP_RES)) {
367 		return;
368 	}
369 
370 	switch (op) {
371 	case OP_SET:
372 		ret = SetVariable(var, val, optionsRef);
373 		break;
374 	case OP_GET:
375 		ret = GetVariable(var, optionsRef);
376 		break;
377 	case OP_DEL:
378 		ret = DeleteVariable(var, optionsRef);
379 		break;
380 	case OP_RES:
381 		ret = SetVariable("ResetNVRam", "1", optionsRef);
382 		break;
383 	case OP_OBL:
384 		// Obliterate NVram (system guid deletes all variables in system region, common guid deletes all non-system variables)
385 		ret = SetVariable(var, "1", optionsRef);
386 		break;
387 	default:
388 		T_FAIL("TestVarOp: Invalid NVRAM operation %d\n", op);
389 		return;
390 	}
391 
392 	// Allow passing in a value other than KERN_SUCCESS || KERN_FAILURE to assert against
393 	// otherwise remain as pass/fail
394 	if ((exp_ret == KERN_SUCCESS) || (exp_ret == KERN_FAILURE)) {
395 		if (ret != KERN_SUCCESS) {
396 			ret = KERN_FAILURE;
397 		}
398 	}
399 
400 	T_ASSERT_EQ(ret, exp_ret, "Operation %s for variable %s returned %s(%#x) as expected\n", GetOpString(op), var, GetRetString(exp_ret), ret);
401 }
402