1/*
2 * Copyright (c) 2005-2007 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_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. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23/*
24 *  BLValidateXMLBootOption.c
25 *  bless
26 *
27 *  Created by Shantonu Sen on 2/7/06.
28 *  Copyright 2006-2007 Apple Inc. All Rights Reserved.
29 *
30 */
31
32#include <IOKit/IOCFUnserialize.h>
33
34#include "bless.h"
35#include "bless_private.h"
36
37#define kBL_GLOBAL_NVRAM_GUID "8BE4DF61-93CA-11D2-AA0D-00E098032B8C"
38
39typedef uint8_t		EFI_UINT8;
40typedef uint16_t	EFI_UINT16;
41typedef uint16_t	EFI_CHAR16;
42typedef uint32_t	EFI_UINT32;
43typedef EFI_UINT8	EFI_DEVICE_PATH_PROTOCOL;
44
45typedef struct _BLESS_EFI_LOAD_OPTION {
46    EFI_UINT32                          Attributes;
47    EFI_UINT16                          FilePathListLength;
48    EFI_CHAR16                          Description[0];
49    EFI_DEVICE_PATH_PROTOCOL            FilePathList[0];
50    EFI_UINT8                           OptionalData[0];
51} BLESS_EFI_LOAD_OPTION;
52
53static int _getBootOptionNumber(BLContextPtr context, io_registry_entry_t options, uint16_t *bootOptionNumber);
54static BLESS_EFI_LOAD_OPTION * _getBootOptionData(BLContextPtr context, io_registry_entry_t options, uint16_t bootOptionNumber, size_t *bootOptionSize);
55static EFI_DEVICE_PATH_PROTOCOL * _getBootDevicePath(BLContextPtr context, io_registry_entry_t options, CFStringRef name, size_t *devicePathSize);
56static CFArrayRef _getBootDeviceXML(BLContextPtr context, io_registry_entry_t options, CFStringRef name);
57
58static int _validate(BLContextPtr context, BLESS_EFI_LOAD_OPTION *bootOption,
59					 size_t bootOptionSize, EFI_DEVICE_PATH_PROTOCOL *devicePath,
60					 size_t devicePathSize, CFArrayRef xmlPath);
61
62int BLValidateXMLBootOption(BLContextPtr context,
63							CFStringRef	 xmlName,
64							CFStringRef	 binaryName)
65{
66
67	io_registry_entry_t optionsNode = 0;
68	uint16_t		bootOptionNumber = 0;
69	int				ret;
70
71    BLESS_EFI_LOAD_OPTION *bootOption = NULL;
72	EFI_DEVICE_PATH_PROTOCOL *devicePath = NULL;
73	size_t				bootOptionSize, devicePathSize;
74	CFArrayRef			xmlPath = NULL;
75
76    optionsNode = IORegistryEntryFromPath(kIOMasterPortDefault, kIODeviceTreePlane ":/options");
77
78    if(IO_OBJECT_NULL == optionsNode) {
79        contextprintf(context, kBLLogLevelError,  "Could not find " kIODeviceTreePlane ":/options\n");
80        return 1;
81    }
82
83	ret = _getBootOptionNumber(context, optionsNode, &bootOptionNumber);
84	if(ret)
85		return 2;
86
87	bootOption = _getBootOptionData(context, optionsNode, bootOptionNumber, &bootOptionSize);
88	if(bootOption == NULL)
89		return 3;
90
91	devicePath = _getBootDevicePath(context, optionsNode, binaryName, &devicePathSize);
92	if(devicePath == NULL)
93		return 4;
94
95	xmlPath = _getBootDeviceXML(context, optionsNode, xmlName);
96	if(xmlPath == NULL)
97		return 5;
98
99	ret = _validate(context, bootOption, bootOptionSize, devicePath, devicePathSize, xmlPath);
100
101	free(bootOption);
102	free(devicePath);
103	CFRelease(xmlPath);
104
105	IOObjectRelease(optionsNode);
106
107	if(ret) {
108		contextprintf(context, kBLLogLevelError,  "Boot option does not match XML representation\n");
109		return 1;
110	} else {
111		contextprintf(context, kBLLogLevelVerbose,  "Boot option matches XML representation\n");
112		return 0;
113	}
114}
115
116static int _getBootOptionNumber(BLContextPtr context, io_registry_entry_t options, uint16_t *bootOptionNumber)
117{
118	CFDataRef       dataRef;
119	const uint16_t	*orderBuffer;
120
121	dataRef = IORegistryEntryCreateCFProperty(options,
122											 CFSTR(kBL_GLOBAL_NVRAM_GUID ":BootOrder"),
123											 kCFAllocatorDefault, 0);
124
125    if(dataRef == NULL) {
126        contextprintf(context, kBLLogLevelError,  "Could not access BootOrder\n");
127        return 2;
128	}
129
130	if(CFGetTypeID(dataRef) != CFDataGetTypeID() || CFDataGetLength(dataRef) < sizeof(uint16_t)) {
131		if(dataRef) CFRelease(dataRef);
132        contextprintf(context, kBLLogLevelError,  "Invalid BootOrder\n");
133		return 2;
134	}
135
136	orderBuffer = (const uint16_t *)CFDataGetBytePtr(dataRef);
137	*bootOptionNumber = CFSwapInt16LittleToHost(*orderBuffer);
138
139	CFRelease(dataRef);
140
141	return 0;
142}
143
144static BLESS_EFI_LOAD_OPTION * _getBootOptionData(BLContextPtr context, io_registry_entry_t options, uint16_t bootOptionNumber, size_t *bootOptionSize)
145{
146    char            bootName[1024];
147	CFStringRef		nvramName;
148	CFDataRef		dataRef;
149	BLESS_EFI_LOAD_OPTION *buffer = NULL;
150
151	snprintf(bootName, sizeof(bootName), "%s:Boot%04hx", kBL_GLOBAL_NVRAM_GUID, bootOptionNumber);
152	contextprintf(context, kBLLogLevelVerbose,  "Boot option is %s\n", bootName);
153
154	nvramName = CFStringCreateWithCString(kCFAllocatorDefault, bootName,kCFStringEncodingUTF8);
155	if(nvramName == NULL) {
156		return NULL;
157	}
158
159	dataRef = IORegistryEntryCreateCFProperty(options,
160											  nvramName,
161											  kCFAllocatorDefault, 0);
162
163    if(dataRef == NULL) {
164		CFRelease(nvramName);
165        contextprintf(context, kBLLogLevelError,  "Could not access Boot%04hx\n", bootOptionNumber);
166        return NULL;
167	}
168
169	CFRelease(nvramName);
170
171	if(CFGetTypeID(dataRef) != CFDataGetTypeID()) {
172		if(dataRef) CFRelease(dataRef);
173        contextprintf(context, kBLLogLevelError,  "Invalid Boot%04hx\n", bootOptionNumber);
174		return NULL;
175	}
176
177	*bootOptionSize = CFDataGetLength(dataRef);
178	buffer = (BLESS_EFI_LOAD_OPTION *)calloc(*bootOptionSize, sizeof(char));
179	if(buffer == NULL)
180		return NULL;
181
182	memcpy(buffer, CFDataGetBytePtr(dataRef), *bootOptionSize);
183
184	CFRelease(dataRef);
185
186	return buffer;
187}
188
189static EFI_DEVICE_PATH_PROTOCOL * _getBootDevicePath(BLContextPtr context, io_registry_entry_t options, CFStringRef name, size_t *devicePathSize)
190{
191	CFDataRef		dataRef;
192	EFI_DEVICE_PATH_PROTOCOL *buffer = NULL;
193
194	dataRef = IORegistryEntryCreateCFProperty(options,
195											  name,
196											  kCFAllocatorDefault, 0);
197
198    if(dataRef == NULL) {
199        contextprintf(context, kBLLogLevelError,  "Could not access boot device\n");
200        return NULL;
201	}
202
203	if(CFGetTypeID(dataRef) != CFDataGetTypeID()) {
204		if(dataRef) CFRelease(dataRef);
205        contextprintf(context, kBLLogLevelError,  "Invalid boot device\n");
206		return NULL;
207	}
208
209	*devicePathSize = CFDataGetLength(dataRef);
210	buffer = (EFI_DEVICE_PATH_PROTOCOL *)calloc(*devicePathSize, sizeof(char));
211	if(buffer == NULL)
212		return NULL;
213
214	memcpy(buffer, CFDataGetBytePtr(dataRef), *devicePathSize);
215
216	CFRelease(dataRef);
217
218	return buffer;
219}
220
221static CFArrayRef _getBootDeviceXML(BLContextPtr context, io_registry_entry_t options, CFStringRef name)
222{
223	int ret;
224	CFStringRef stringVal = NULL;
225	char        buffer[1024];
226	CFArrayRef	arrayRef;
227
228	ret = BLCopyEFINVRAMVariableAsString(context, name, &stringVal);
229	if(ret || stringVal == NULL) {
230		return NULL;
231	}
232
233    if(!CFStringGetCString(stringVal, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
234		CFRelease(stringVal);
235        return NULL;
236    }
237
238	CFRelease(stringVal);
239
240    arrayRef = IOCFUnserialize(buffer,
241                               kCFAllocatorDefault,
242                               0,
243                               NULL);
244    if(arrayRef == NULL) {
245        contextprintf(context, kBLLogLevelError, "Could not unserialize string\n");
246        return NULL;
247    }
248
249    if(CFGetTypeID(arrayRef) != CFArrayGetTypeID()) {
250        CFRelease(arrayRef);
251        contextprintf(context, kBLLogLevelError, "Bad type in XML string\n");
252        return NULL;
253    }
254
255
256	return arrayRef;
257}
258
259static int _validate(BLContextPtr context, BLESS_EFI_LOAD_OPTION *bootOption,
260					 size_t bootOptionSize, EFI_DEVICE_PATH_PROTOCOL *devicePath,
261					 size_t devicePathSize, CFArrayRef xmlPath)
262{
263	EFI_CHAR16		*description;
264	int				i;
265	char			debugDesc[100];
266	EFI_DEVICE_PATH_PROTOCOL	*bootDev;
267	EFI_UINT8		*OptionalData;
268	size_t			OptionalDataSize;
269	CFIndex			j, count;
270	size_t			bufferSize = 0;
271	EFI_UINT8		*buffer = NULL;
272
273
274	description = bootOption->Description;
275
276	for(i=0; description[i]; i++) {
277		debugDesc[i] = (char)CFSwapInt16LittleToHost(description[i]);
278	}
279	debugDesc[i] = '\0';
280
281	// i is the size of the description string
282	contextprintf(context, kBLLogLevelVerbose, "Processing boot option '%s'\n", debugDesc);
283
284	bootDev = (EFI_DEVICE_PATH_PROTOCOL	*)&bootOption->Description[i+1];
285	bootOption->FilePathListLength = CFSwapInt16LittleToHost(bootOption->FilePathListLength);
286
287	if((bootOption->FilePathListLength != devicePathSize)
288	   || (0 != memcmp(bootDev, devicePath, devicePathSize))) {
289		contextprintf(context, kBLLogLevelVerbose, "Boot device path incorrect\n");
290		return 1;
291	}
292
293	OptionalData = ((EFI_UINT8 *)bootDev) + bootOption->FilePathListLength;
294	OptionalDataSize = bootOptionSize - ((intptr_t)OptionalData - (intptr_t)bootOption);
295
296	count = CFArrayGetCount(xmlPath);
297	for(j=0; j < count; j++) {
298		CFDictionaryRef element = CFArrayGetValueAtIndex(xmlPath, j);
299		CFTypeRef	val;
300
301		if(CFGetTypeID(element) != CFDictionaryGetTypeID())
302			continue;
303
304		val = CFDictionaryGetValue(element, CFSTR("IOEFIBootOption"));
305		if(val == NULL)
306			continue;
307
308		if(CFGetTypeID(val) == CFStringGetTypeID()) {
309			bufferSize = (CFStringGetLength(val)+1)*2;
310			buffer = (EFI_UINT8 *)calloc(bufferSize, sizeof(char));
311
312			if(!CFStringGetCString(val, (char *)buffer, bufferSize, kCFStringEncodingUTF16LE)) {
313				free(buffer);
314				buffer = NULL;
315				bufferSize = 0;
316				continue;
317			}
318
319		} else if(CFGetTypeID(val) == CFDataGetTypeID()) {
320			bufferSize = CFDataGetLength(val);
321			buffer = (EFI_UINT8 *)calloc(bufferSize, sizeof(char));
322
323			memcpy(buffer, CFDataGetBytePtr(val), bufferSize);
324		}
325
326	}
327
328	// if either the boot option or the XML has this, we need to validate
329	if(OptionalDataSize || bufferSize) {
330		if((OptionalDataSize != bufferSize)
331		   || (0 != memcmp(OptionalData, buffer, bufferSize))) {
332			contextprintf(context, kBLLogLevelVerbose, "Optional data incorrect\n");
333			free(buffer);
334			return 2;
335		}
336	}
337
338	if(buffer)
339		free(buffer);
340
341	return 0;
342}
343
344