/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #if defined(__linux__) #include #elif defined(__FreeBSD__) /* although against style, the order seems to matter on FreeBSD */ /* clang-format off */ #include #include /* clang-format on */ #endif #include #include "fu-uefi-devpath.h" #if defined(__FreeBSD__) #define EFIDP_END_TYPE 0x7f #define EFIDP_END_ENTIRE 0xff #endif typedef struct { guint8 type; guint8 subtype; GBytes *data; } FuUefiDevPath; static void fu_uefi_efi_dp_free(FuUefiDevPath *dp) { if (dp->data != NULL) g_bytes_unref(dp->data); g_free(dp); } GBytes * fu_uefi_devpath_find_data(GPtrArray *dps, guint8 type, guint8 subtype, GError **error) { for (guint i = 0; i < dps->len; i++) { FuUefiDevPath *dp = g_ptr_array_index(dps, i); if (dp->type == type && dp->subtype == subtype) return dp->data; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no DP with type 0x%02x and subtype 0x%02x", type, subtype); return NULL; } GPtrArray * fu_uefi_devpath_parse(const guint8 *buf, gsize sz, FuUefiDevpathParseFlags flags, GError **error) { guint16 offset = 0; g_autoptr(GPtrArray) dps = NULL; /* sanity check */ if (sz < sizeof(efidp_header)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "const_efidp is corrupt"); return NULL; } dps = g_ptr_array_new_with_free_func((GDestroyNotify)fu_uefi_efi_dp_free); while (1) { FuUefiDevPath *dp; const efidp_header *hdr = (efidp_header *)(buf + offset); guint16 hdr_length = GUINT16_FROM_LE(hdr->length); /* check if last entry */ g_debug("DP type:0x%02x subtype:0x%02x size:0x%04x", hdr->type, hdr->subtype, hdr->length); if (hdr->type == EFIDP_END_TYPE && hdr->subtype == EFIDP_END_ENTIRE) break; /* work around a bug in efi_va_generate_file_device_path_from_esp */ if (offset + sizeof(efidp_header) + hdr->length > sz) { hdr_length = 0; fu_common_dump_full(G_LOG_DOMAIN, "efidp", buf + offset, sz - offset, 32, FU_DUMP_FLAGS_SHOW_ADDRESSES); for (guint16 i = offset + 4; i <= sz - 4; i++) { if (memcmp(buf + i, "\x7f\xff\x04\x00", 4) == 0) { hdr_length = i - offset; g_debug("found END_ENTIRE at 0x%04x", (guint)(i - offset)); break; } } if (hdr_length == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "DP length invalid and no END_ENTIRE " "found, possibly data truncation?"); return NULL; } if ((flags & FU_UEFI_DEVPATH_PARSE_FLAG_REPAIR) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "DP length invalid, reported 0x%04x, maybe 0x%04x", hdr->length, hdr_length); return NULL; } g_debug("DP length invalid! Truncating from 0x%04x to 0x%04x", hdr->length, hdr_length); } /* add new DP */ dp = g_new0(FuUefiDevPath, 1); dp->type = hdr->type; dp->subtype = hdr->subtype; if (hdr_length > 0) dp->data = g_bytes_new(buf + offset + 4, hdr_length); g_ptr_array_add(dps, dp); /* advance to next DP */ offset += hdr_length; if (offset + sizeof(efidp_header) > sz) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "DP length invalid after fixing"); return NULL; } } return g_steal_pointer(&dps); }