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