Heinrich Schuchardt | ee3d1e0 | 2020-12-08 15:55:01 +0100 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * efi_selftest_load_file |
| 4 | * |
| 5 | * Copyright (c) 2020 Heinrich Schuchardt <xypron.glpk@gmx.de> |
| 6 | * |
| 7 | * This test checks the handling of the LOAD_FILE and the LOAD_FILE2 protocol |
| 8 | * by the LoadImage() service. |
| 9 | */ |
| 10 | |
| 11 | #include <efi_selftest.h> |
| 12 | /* Include containing the miniapp.efi application */ |
| 13 | #include "efi_miniapp_file_image_exit.h" |
| 14 | |
| 15 | /* Block size of compressed disk image */ |
| 16 | #define COMPRESSED_DISK_IMAGE_BLOCK_SIZE 8 |
| 17 | |
| 18 | /* Binary logarithm of the block size */ |
| 19 | #define LB_BLOCK_SIZE 9 |
| 20 | |
| 21 | #define GUID_VENDOR \ |
| 22 | EFI_GUID(0xdbca4c98, 0x6cb0, 0x694d, \ |
| 23 | 0x08, 0x72, 0x81, 0x9c, 0x65, 0xfc, 0xbb, 0xd1) |
| 24 | |
| 25 | #define GUID_VENDOR2 \ |
| 26 | EFI_GUID(0xdbca4c98, 0x6cb0, 0x694d, \ |
| 27 | 0x08, 0x72, 0x81, 0x9c, 0x65, 0xfc, 0xbb, 0xd2) |
| 28 | |
| 29 | #define FILE_NAME_SIZE 16 |
| 30 | |
| 31 | static const efi_guid_t efi_st_guid_load_file_protocol = |
| 32 | EFI_LOAD_FILE_PROTOCOL_GUID; |
| 33 | static const efi_guid_t efi_st_guid_load_file2_protocol = |
| 34 | EFI_LOAD_FILE2_PROTOCOL_GUID; |
| 35 | static const efi_guid_t efi_st_guid_device_path = |
| 36 | EFI_DEVICE_PATH_PROTOCOL_GUID; |
| 37 | |
| 38 | static efi_handle_t image_handle; |
| 39 | static struct efi_boot_services *boottime; |
| 40 | static efi_handle_t handle_lf; |
| 41 | static efi_handle_t handle_lf2; |
| 42 | |
| 43 | /* One 8 byte block of the compressed disk image */ |
| 44 | struct line { |
| 45 | size_t addr; |
| 46 | char *line; |
| 47 | }; |
| 48 | |
| 49 | /* Compressed file image */ |
| 50 | struct compressed_file_image { |
| 51 | size_t length; |
| 52 | struct line lines[]; |
| 53 | }; |
| 54 | |
| 55 | static struct compressed_file_image img = EFI_ST_DISK_IMG; |
| 56 | |
| 57 | static int load_file_call_count; |
| 58 | static int load_file2_call_count; |
| 59 | |
| 60 | /* Decompressed file image */ |
| 61 | static u8 *image; |
| 62 | |
| 63 | static struct { |
| 64 | struct efi_device_path_vendor v; |
| 65 | struct efi_device_path d; |
| 66 | } dp_lf_prot = { |
| 67 | { |
| 68 | { |
| 69 | DEVICE_PATH_TYPE_HARDWARE_DEVICE, |
| 70 | DEVICE_PATH_SUB_TYPE_VENDOR, |
| 71 | sizeof(struct efi_device_path_vendor), |
| 72 | }, |
| 73 | GUID_VENDOR, |
| 74 | }, |
| 75 | { |
| 76 | DEVICE_PATH_TYPE_END, |
| 77 | DEVICE_PATH_SUB_TYPE_END, |
| 78 | sizeof(struct efi_device_path), |
| 79 | }, |
| 80 | }; |
| 81 | |
| 82 | static struct { |
| 83 | struct efi_device_path_vendor v; |
| 84 | struct efi_device_path_file_path f; |
| 85 | u16 file_name[FILE_NAME_SIZE]; |
| 86 | struct efi_device_path e; |
| 87 | } dp_lf_file = { |
| 88 | { |
| 89 | { |
| 90 | DEVICE_PATH_TYPE_HARDWARE_DEVICE, |
| 91 | DEVICE_PATH_SUB_TYPE_VENDOR, |
| 92 | sizeof(struct efi_device_path_vendor), |
| 93 | }, |
| 94 | GUID_VENDOR, |
| 95 | }, |
| 96 | { |
| 97 | { |
| 98 | DEVICE_PATH_TYPE_MEDIA_DEVICE, |
| 99 | DEVICE_PATH_SUB_TYPE_FILE_PATH, |
| 100 | sizeof(struct efi_device_path_file_path) + |
| 101 | FILE_NAME_SIZE * sizeof(u16), |
| 102 | } |
| 103 | }, |
| 104 | L"\\lf.efi", |
| 105 | { |
| 106 | DEVICE_PATH_TYPE_END, |
| 107 | DEVICE_PATH_SUB_TYPE_END, |
| 108 | sizeof(struct efi_device_path), |
| 109 | }, |
| 110 | }; |
| 111 | |
| 112 | struct efi_device_path *dp_lf_file_remainder = &dp_lf_file.f.dp; |
| 113 | |
| 114 | static struct { |
| 115 | struct efi_device_path_vendor v; |
| 116 | struct efi_device_path d; |
| 117 | } dp_lf2_prot = { |
| 118 | { |
| 119 | { |
| 120 | DEVICE_PATH_TYPE_HARDWARE_DEVICE, |
| 121 | DEVICE_PATH_SUB_TYPE_VENDOR, |
| 122 | sizeof(struct efi_device_path_vendor), |
| 123 | }, |
| 124 | GUID_VENDOR2, |
| 125 | }, |
| 126 | { |
| 127 | DEVICE_PATH_TYPE_END, |
| 128 | DEVICE_PATH_SUB_TYPE_END, |
| 129 | sizeof(struct efi_device_path), |
| 130 | }, |
| 131 | }; |
| 132 | |
| 133 | static struct { |
| 134 | struct efi_device_path_vendor v; |
| 135 | struct efi_device_path_file_path f; |
| 136 | u16 file_name[FILE_NAME_SIZE]; |
| 137 | struct efi_device_path e; |
| 138 | } dp_lf2_file = { |
| 139 | { |
| 140 | { |
| 141 | DEVICE_PATH_TYPE_HARDWARE_DEVICE, |
| 142 | DEVICE_PATH_SUB_TYPE_VENDOR, |
| 143 | sizeof(struct efi_device_path_vendor), |
| 144 | }, |
| 145 | GUID_VENDOR2, |
| 146 | }, |
| 147 | { |
| 148 | { |
| 149 | DEVICE_PATH_TYPE_MEDIA_DEVICE, |
| 150 | DEVICE_PATH_SUB_TYPE_FILE_PATH, |
| 151 | sizeof(struct efi_device_path_file_path) + |
| 152 | FILE_NAME_SIZE * sizeof(u16), |
| 153 | } |
| 154 | }, |
| 155 | L"\\lf2.efi", |
| 156 | { |
| 157 | DEVICE_PATH_TYPE_END, |
| 158 | DEVICE_PATH_SUB_TYPE_END, |
| 159 | sizeof(struct efi_device_path), |
| 160 | }, |
| 161 | }; |
| 162 | |
| 163 | struct efi_device_path *dp_lf2_file_remainder = &dp_lf2_file.f.dp; |
| 164 | |
| 165 | /* |
| 166 | * Decompress the disk image. |
| 167 | * |
| 168 | * @image decompressed disk image |
| 169 | * @return status code |
| 170 | */ |
| 171 | static efi_status_t decompress(u8 **image) |
| 172 | { |
| 173 | u8 *buf; |
| 174 | size_t i; |
| 175 | size_t addr; |
| 176 | size_t len; |
| 177 | efi_status_t ret; |
| 178 | |
| 179 | ret = boottime->allocate_pool(EFI_LOADER_DATA, img.length, |
| 180 | (void **)&buf); |
| 181 | if (ret != EFI_SUCCESS) { |
| 182 | efi_st_error("Out of memory\n"); |
| 183 | return ret; |
| 184 | } |
| 185 | boottime->set_mem(buf, img.length, 0); |
| 186 | |
| 187 | for (i = 0; ; ++i) { |
| 188 | if (!img.lines[i].line) |
| 189 | break; |
| 190 | addr = img.lines[i].addr; |
| 191 | len = COMPRESSED_DISK_IMAGE_BLOCK_SIZE; |
| 192 | if (addr + len > img.length) |
| 193 | len = img.length - addr; |
| 194 | boottime->copy_mem(buf + addr, img.lines[i].line, len); |
| 195 | } |
| 196 | *image = buf; |
| 197 | return ret; |
| 198 | } |
| 199 | |
| 200 | /* |
| 201 | * load_file() - LoadFile() service of a EFI_LOAD_FILE_PROTOCOL |
| 202 | * |
| 203 | * @this: instance of EFI_LOAD_FILE_PROTOCOL |
| 204 | * @file_path: remaining device path |
| 205 | * @boot_policy: true if called by boot manager |
| 206 | * @buffer_size: (required) buffer size |
| 207 | * @buffer: buffer to which the file is to be loaded |
| 208 | */ |
| 209 | efi_status_t EFIAPI load_file(struct efi_load_file_protocol *this, |
| 210 | struct efi_device_path *file_path, |
| 211 | bool boot_policy, |
| 212 | efi_uintn_t *buffer_size, |
| 213 | void *buffer) |
| 214 | { |
| 215 | ++load_file_call_count; |
| 216 | if (memcmp(file_path, dp_lf_file_remainder, |
| 217 | sizeof(struct efi_device_path_file_path) + |
| 218 | FILE_NAME_SIZE * sizeof(u16) + |
| 219 | sizeof(struct efi_device_path))) { |
| 220 | efi_st_error("Wrong remaining device path\n"); |
| 221 | return EFI_NOT_FOUND; |
| 222 | } |
| 223 | if (this->load_file != load_file) { |
| 224 | efi_st_error("wrong this\n"); |
| 225 | return EFI_INVALID_PARAMETER; |
| 226 | } |
| 227 | if (*buffer_size < img.length) { |
| 228 | *buffer_size = img.length; |
| 229 | return EFI_BUFFER_TOO_SMALL; |
| 230 | } |
| 231 | memcpy(buffer, image, img.length); |
| 232 | *buffer_size = img.length; |
| 233 | return EFI_SUCCESS; |
| 234 | } |
| 235 | |
| 236 | |
| 237 | /* |
| 238 | * load_file2() - LoadFile() service of a EFI_LOAD_FILE2_PROTOCOL |
| 239 | * |
| 240 | * @this: instance of EFI_LOAD_FILE2_PROTOCOL |
| 241 | * @file_path: remaining device path |
| 242 | * @boot_policy: true if called by boot manager |
| 243 | * @buffer_size: (required) buffer size |
| 244 | * @buffer: buffer to which the file is to be loaded |
| 245 | */ |
| 246 | efi_status_t EFIAPI load_file2(struct efi_load_file_protocol *this, |
| 247 | struct efi_device_path *file_path, |
| 248 | bool boot_policy, |
| 249 | efi_uintn_t *buffer_size, |
| 250 | void *buffer) |
| 251 | { |
| 252 | ++load_file2_call_count; |
| 253 | if (memcmp(file_path, dp_lf2_file_remainder, |
| 254 | sizeof(struct efi_device_path_file_path) + |
| 255 | FILE_NAME_SIZE * sizeof(u16) + |
| 256 | sizeof(struct efi_device_path))) { |
| 257 | efi_st_error("Wrong remaining device path\n"); |
| 258 | return EFI_NOT_FOUND; |
| 259 | } |
| 260 | if (this->load_file != load_file2) { |
| 261 | efi_st_error("wrong this\n"); |
| 262 | return EFI_INVALID_PARAMETER; |
| 263 | } |
| 264 | if (boot_policy) { |
| 265 | efi_st_error("LOAD_FILE2 called with boot_policy = true"); |
| 266 | return EFI_INVALID_PARAMETER; |
| 267 | } |
| 268 | if (*buffer_size < img.length) { |
| 269 | *buffer_size = img.length; |
| 270 | return EFI_BUFFER_TOO_SMALL; |
| 271 | } |
| 272 | memcpy(buffer, image, img.length); |
| 273 | *buffer_size = img.length; |
| 274 | return EFI_SUCCESS; |
| 275 | } |
| 276 | |
| 277 | static struct efi_load_file_protocol lf_prot = {load_file}; |
| 278 | static struct efi_load_file_protocol lf2_prot = {load_file2}; |
| 279 | |
| 280 | /* |
| 281 | * Setup unit test. |
| 282 | * |
| 283 | * Install an EFI_LOAD_FILE_PROTOCOL and an EFI_LOAD_FILE2_PROTOCOL. |
| 284 | * |
| 285 | * @handle: handle of the loaded image |
| 286 | * @systable: system table |
| 287 | * @return: EFI_ST_SUCCESS for success |
| 288 | */ |
| 289 | static int efi_st_load_file_setup(const efi_handle_t handle, |
| 290 | const struct efi_system_table *systable) |
| 291 | { |
| 292 | efi_status_t ret; |
| 293 | |
| 294 | image_handle = handle; |
| 295 | boottime = systable->boottime; |
| 296 | |
| 297 | /* Load the application image into memory */ |
| 298 | decompress(&image); |
| 299 | |
| 300 | ret = boottime->install_multiple_protocol_interfaces( |
| 301 | &handle_lf, |
| 302 | &efi_st_guid_device_path, |
| 303 | &dp_lf_prot, |
| 304 | &efi_st_guid_load_file_protocol, |
| 305 | &lf_prot, |
| 306 | NULL); |
| 307 | if (ret != EFI_SUCCESS) { |
| 308 | efi_st_error("InstallMultipleProtocolInterfaces failed\n"); |
| 309 | return EFI_ST_FAILURE; |
| 310 | } |
| 311 | ret = boottime->install_multiple_protocol_interfaces( |
| 312 | &handle_lf2, |
| 313 | &efi_st_guid_device_path, |
| 314 | &dp_lf2_prot, |
| 315 | &efi_st_guid_load_file2_protocol, |
| 316 | &lf2_prot, |
| 317 | NULL); |
| 318 | if (ret != EFI_SUCCESS) { |
| 319 | efi_st_error("InstallMultipleProtocolInterfaces failed\n"); |
| 320 | return EFI_ST_FAILURE; |
| 321 | } |
| 322 | |
| 323 | return EFI_ST_SUCCESS; |
| 324 | } |
| 325 | |
| 326 | /* |
| 327 | * Tear down unit test. |
| 328 | * |
| 329 | * @return: EFI_ST_SUCCESS for success |
| 330 | */ |
| 331 | static int efi_st_load_file_teardown(void) |
| 332 | { |
| 333 | efi_status_t ret = EFI_ST_SUCCESS; |
| 334 | |
| 335 | if (handle_lf) { |
| 336 | ret = boottime->uninstall_multiple_protocol_interfaces( |
| 337 | handle_lf, |
| 338 | &efi_st_guid_device_path, |
| 339 | &dp_lf_prot, |
| 340 | &efi_st_guid_load_file_protocol, |
| 341 | &lf_prot, |
| 342 | NULL); |
| 343 | if (ret != EFI_SUCCESS) { |
| 344 | efi_st_error( |
| 345 | "UninstallMultipleProtocolInterfaces failed\n"); |
| 346 | return EFI_ST_FAILURE; |
| 347 | } |
| 348 | } |
| 349 | if (handle_lf2) { |
| 350 | ret = boottime->uninstall_multiple_protocol_interfaces( |
| 351 | handle_lf2, |
| 352 | &efi_st_guid_device_path, |
| 353 | &dp_lf2_prot, |
| 354 | &efi_st_guid_load_file2_protocol, |
| 355 | &lf2_prot, |
| 356 | NULL); |
| 357 | if (ret != EFI_SUCCESS) { |
| 358 | efi_st_error( |
| 359 | "UninstallMultipleProtocolInterfaces failed\n"); |
| 360 | return EFI_ST_FAILURE; |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | if (image) { |
| 365 | ret = boottime->free_pool(image); |
| 366 | if (ret != EFI_SUCCESS) { |
| 367 | efi_st_error("Failed to free image\n"); |
| 368 | return EFI_ST_FAILURE; |
| 369 | } |
| 370 | } |
| 371 | return ret; |
| 372 | } |
| 373 | |
| 374 | /* |
| 375 | * Execute unit test. |
| 376 | * |
| 377 | * Try loading an image via the EFI_LOAD_FILE_PROTOCOL and the |
| 378 | * EFI_LOAD_FILE2_PROTOCOL. Finally execute the image. |
| 379 | * |
| 380 | * @return: EFI_ST_SUCCESS for success |
| 381 | */ |
| 382 | static int efi_st_load_file_execute(void) |
| 383 | { |
| 384 | efi_status_t ret; |
| 385 | efi_handle_t handle; |
| 386 | efi_uintn_t exit_data_size = 0; |
| 387 | u16 *exit_data = NULL; |
| 388 | u16 expected_text[] = EFI_ST_SUCCESS_STR; |
| 389 | |
| 390 | load_file_call_count = 0; |
| 391 | load_file2_call_count = 0; |
| 392 | handle = NULL; |
| 393 | ret = boottime->load_image(true, image_handle, &dp_lf_file.v.dp, NULL, |
| 394 | 0, &handle); |
| 395 | if (ret != EFI_SUCCESS) { |
| 396 | efi_st_error("Failed to load image\n"); |
| 397 | return EFI_ST_FAILURE; |
| 398 | } |
| 399 | if (load_file2_call_count || !load_file_call_count) { |
| 400 | efi_st_error("Wrong image loaded\n"); |
| 401 | return EFI_ST_FAILURE; |
| 402 | } |
| 403 | ret = boottime->unload_image(handle); |
| 404 | if (ret != EFI_SUCCESS) { |
| 405 | efi_st_error("Failed to unload image\n"); |
| 406 | return EFI_ST_FAILURE; |
| 407 | } |
| 408 | |
| 409 | load_file_call_count = 0; |
| 410 | load_file2_call_count = 0; |
| 411 | handle = NULL; |
| 412 | ret = boottime->load_image(false, image_handle, &dp_lf_file.v.dp, NULL, |
| 413 | 0, &handle); |
| 414 | if (ret != EFI_SUCCESS) { |
| 415 | efi_st_error("Failed to load image\n"); |
| 416 | return EFI_ST_FAILURE; |
| 417 | } |
| 418 | if (load_file2_call_count || !load_file_call_count) { |
| 419 | efi_st_error("Wrong image loaded\n"); |
| 420 | return EFI_ST_FAILURE; |
| 421 | } |
| 422 | ret = boottime->unload_image(handle); |
| 423 | if (ret != EFI_SUCCESS) { |
| 424 | efi_st_error("Failed to unload image\n"); |
| 425 | return EFI_ST_FAILURE; |
| 426 | } |
| 427 | |
| 428 | ret = boottime->load_image(true, image_handle, &dp_lf2_file.v.dp, NULL, |
| 429 | 0, &handle); |
| 430 | if (ret != EFI_NOT_FOUND) { |
| 431 | efi_st_error( |
| 432 | "Boot manager should not use LOAD_FILE2_PROTOCOL\n"); |
| 433 | return EFI_ST_FAILURE; |
| 434 | } |
| 435 | |
| 436 | load_file_call_count = 0; |
| 437 | load_file2_call_count = 0; |
| 438 | handle = NULL; |
| 439 | ret = boottime->load_image(false, image_handle, &dp_lf2_file.v.dp, NULL, |
| 440 | 0, &handle); |
| 441 | if (ret != EFI_SUCCESS) { |
| 442 | efi_st_error("Failed to load image\n"); |
| 443 | return EFI_ST_FAILURE; |
| 444 | } |
| 445 | if (!load_file2_call_count || load_file_call_count) { |
| 446 | efi_st_error("Wrong image loaded\n"); |
| 447 | return EFI_ST_FAILURE; |
| 448 | } |
| 449 | |
| 450 | ret = boottime->start_image(handle, &exit_data_size, &exit_data); |
| 451 | if (ret != EFI_UNSUPPORTED) { |
| 452 | efi_st_error("Wrong return value from application\n"); |
| 453 | return EFI_ST_FAILURE; |
| 454 | } |
| 455 | if (!exit_data || exit_data_size != sizeof(expected_text) || |
| 456 | memcmp(exit_data, expected_text, sizeof(expected_text))) { |
| 457 | efi_st_error("Incorrect exit data\n"); |
| 458 | return EFI_ST_FAILURE; |
| 459 | } |
| 460 | ret = boottime->free_pool(exit_data); |
| 461 | if (ret != EFI_SUCCESS) { |
| 462 | efi_st_error("Failed to free exit data\n"); |
| 463 | return EFI_ST_FAILURE; |
| 464 | } |
| 465 | |
| 466 | return EFI_ST_SUCCESS; |
| 467 | } |
| 468 | |
| 469 | EFI_UNIT_TEST(load_file_protocol) = { |
| 470 | .name = "load file protocol", |
| 471 | .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, |
| 472 | .setup = efi_st_load_file_setup, |
| 473 | .execute = efi_st_load_file_execute, |
| 474 | .teardown = efi_st_load_file_teardown, |
| 475 | }; |