diff --git a/contrib/ci/dependencies.xml b/contrib/ci/dependencies.xml index a7d850a51..2f5af832b 100644 --- a/contrib/ci/dependencies.xml +++ b/contrib/ci/dependencies.xml @@ -339,6 +339,24 @@ + + + gnu-efi-libs + + + + + + + + + gnu-efi + gnu-efi + + + gnu-efi + + diff --git a/contrib/debian/fwupd.install b/contrib/debian/fwupd.install index 0c4d02b74..1c5cd4405 100644 --- a/contrib/debian/fwupd.install +++ b/contrib/debian/fwupd.install @@ -9,6 +9,7 @@ usr/share/locale usr/share/metainfo/* usr/lib/*/fwupd usr/lib/*/fwupdtool +usr/lib/*/efi usr/share/man/man1/* lib/systemd/system/* var/lib/fwupd diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index a66183a9b..61fc7bf77 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -63,6 +63,7 @@ BuildRequires: freetype BuildRequires: fontconfig BuildRequires: dejavu-sans-fonts BuildRequires: adobe-source-han-sans-cn-fonts +BuildRequires: gnu-efi-devel %endif %if 0%{?have_dell} @@ -191,6 +192,9 @@ mkdir -p --mode=0700 $RPM_BUILD_ROOT%{_localstatedir}/lib/fwupd/gnupg %dir %{_libexecdir}/fwupd %{_libexecdir}/fwupd/fwupd %{_libexecdir}/fwupd/fwupdtool +%if 0%{?have_uefi} +%{_libexecdir}/fwupd/efi/*.efi +%endif %{_bindir}/dfu-tool %{_bindir}/fwupdmgr %dir %{_sysconfdir}/fwupd diff --git a/contrib/org.freedesktop.fwupd.json b/contrib/org.freedesktop.fwupd.json index 565c3c4f8..4adfba8d6 100644 --- a/contrib/org.freedesktop.fwupd.json +++ b/contrib/org.freedesktop.fwupd.json @@ -144,6 +144,21 @@ } ] }, + { + "name": "gnu-efi", + "buildsystem": "simple", + "build-commands": ["make", "make PREFIX=/app install"], + "cleanup": [ + "/bin/efivar" + ], + "sources": [ + { + "type": "archive", + "url": "http://superb-dca2.dl.sourceforge.net/project/gnu-efi/gnu-efi-3.0.5.tar.bz2", + "sha256": "bd8fcd5914f18fc0e4ba948ab03b00013e528504f529c60739b748f6ef130b22" + } + ] + }, { "name": "fwupd", "buildsystem": "meson", @@ -157,6 +172,9 @@ "-Dplugin_uefi_labels=false", "-Dsystemd=false", "-Dtests=false", + "-Defi-includedir=/app/include/efi", + "-Defi-ldsdir=/app/lib", + "-Defi-libdir=/app/lib", "--sysconfdir=/app/etc", "--localstatedir=/var/data"], "cleanup": [ diff --git a/contrib/snap/snapcraft-master.yaml b/contrib/snap/snapcraft-master.yaml index 01b790ede..46bed2e2a 100644 --- a/contrib/snap/snapcraft-master.yaml +++ b/contrib/snap/snapcraft-master.yaml @@ -187,10 +187,22 @@ parts: - -usr/lib/*/pkgconfig - -usr/lib/*/girepository-1.0 after: [libusb] + gnu-efi: + plugin: make + source: http://superb-dca2.dl.sourceforge.net/project/gnu-efi/gnu-efi-3.0.5.tar.bz2 + make-parameters: + - PREFIX=/usr + make-install-var: INSTALLROOT + prime: + - -usr/include/ + - -usr/lib fwupd: plugin: meson meson-parameters: [--prefix=/usr, --libexecdir=/usr/lib, + -Defi-includedir=$SNAPCRAFT_STAGE/usr/include/efi, + -Defi-ldsdir=$SNAPCRAFT_STAGE/usr/lib, + -Defi-libdir=$SNAPCRAFT_STAGE/usr/lib, -Dtests=false, -Ddaemon=false, -Dgtkdoc=false, @@ -263,7 +275,7 @@ parts: - -usr/share/upstart - -usr/lib/*/glib-2.0 - -usr/lib/*/pkgconfig - after: [appstream-glib-dev, libfwup-dev, gudev, gusb] + after: [appstream-glib-dev, libfwup-dev, gudev, gusb, gnu-efi, libsmbios, libefivar-fixpkgconfig] fix-bash-completion: plugin: make source: contrib/snap/fix-bash-completion diff --git a/contrib/snap/snapcraft-stable.yaml b/contrib/snap/snapcraft-stable.yaml index 56225ef57..4d9c6aca6 100644 --- a/contrib/snap/snapcraft-stable.yaml +++ b/contrib/snap/snapcraft-stable.yaml @@ -183,10 +183,22 @@ parts: - -usr/lib/*/pkgconfig - -usr/lib/*/girepository-1.0 after: [libusb] + gnu-efi: + plugin: make + source: http://superb-dca2.dl.sourceforge.net/project/gnu-efi/gnu-efi-3.0.5.tar.bz2 + make-parameters: + - PREFIX=/usr + make-install-var: INSTALLROOT + prime: + - -usr/include/ + - -usr/lib fwupd: plugin: meson meson-parameters: [--prefix=/usr, --libexecdir=/usr/lib, + -Defi-includedir=$SNAPCRAFT_STAGE/usr/include/efi, + -Defi-ldsdir=$SNAPCRAFT_STAGE/usr/lib, + -Defi-libdir=$SNAPCRAFT_STAGE/usr/lib, -Dtests=false, -Ddaemon=false, -Dgtkdoc=false, @@ -259,7 +271,7 @@ parts: - -usr/share/upstart - -usr/lib/*/glib-2.0 - -usr/lib/*/pkgconfig - after: [appstream-glib-dev, libfwup-dev, gudev, gusb] + after: [appstream-glib-dev, libfwup-dev, gudev, gusb, gnu-efi, libefivar-fixpkgconfig, libsmbios] fix-bash-completion: plugin: make source: contrib/snap/fix-bash-completion diff --git a/meson.build b/meson.build index b4a45d4cb..7fcba33a0 100644 --- a/meson.build +++ b/meson.build @@ -214,9 +214,34 @@ if get_option('plugin_uefi') if fwup.version().version_compare('>= 12') conf.set('HAVE_FWUP_VERSION', '1') endif + conf.set_quoted('LIBFWUP_LIBRARY_VERSION', fwup.version()) + efivar = dependency('efivar') conf.set_quoted('EFIVAR_LIBRARY_VERSION', efivar.version()) - conf.set_quoted('LIBFWUP_LIBRARY_VERSION', fwup.version()) + objcopy = find_program ('objcopy') + readelf = find_program ('readelf') + + efi_app_location = join_paths(libexecdir, 'fwupd', 'efi') + conf.set_quoted ('EFI_APP_LOCATION', efi_app_location) + + efi_arch = host_machine.cpu_family() + if efi_arch == 'x86' + EFI_MACHINE_TYPE_NAME = 'ia32' + gnu_efi_arch = 'ia32' + elif efi_arch == 'x86_64' + EFI_MACHINE_TYPE_NAME = 'x64' + gnu_efi_arch = 'x86_64' + elif efi_arch == 'arm' + EFI_MACHINE_TYPE_NAME = 'arm' + gnu_efi_arch = 'arm' + elif efi_arch == 'aarch64' + EFI_MACHINE_TYPE_NAME = 'aa64' + gnu_efi_arch = 'aarch64' + else + EFI_MACHINE_TYPE_NAME = '' + gnu_efi_arch = '' + endif + conf.set_quoted('EFI_MACHINE_TYPE_NAME', EFI_MACHINE_TYPE_NAME) endif if get_option('plugin_dell') diff --git a/meson_options.txt b/meson_options.txt index 3442677ea..b9361d001 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -19,3 +19,8 @@ option('systemd', type : 'boolean', value : true, description : 'enable systemd option('systemdunitdir', type: 'string', value: '', description: 'Directory for systemd units') option('tests', type : 'boolean', value : true, description : 'enable tests') option('udevdir', type: 'string', value: '', description: 'Directory for udev rules') +option('efi-cc', type : 'string', value : 'gcc', description : 'the compiler to use for EFI modules') +option('efi-ld', type : 'string', value : 'ld', description : 'the linker to use for EFI modules') +option('efi-libdir', type : 'string', description : 'path to the EFI lib directory') +option('efi-ldsdir', type : 'string', description : 'path to the EFI lds directory') +option('efi-includedir', type : 'string', value : '/usr/include/efi', description : 'path to the EFI header directory') diff --git a/plugins/uefi/efi/fwup-efi.h b/plugins/uefi/efi/fwup-efi.h new file mode 100644 index 000000000..5d55a85b1 --- /dev/null +++ b/plugins/uefi/efi/fwup-efi.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015-2017 Peter Jones + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef _FWUP_EFI_H +#define _FWUP_EFI_H + +#define FWUPDATE_ATTEMPT_UPDATE 0x00000001 +#define FWUPDATE_ATTEMPTED 0x00000002 + +#define UPDATE_INFO_VERSION 7 + +#ifdef _EFI_INCLUDE_ +#define efidp_header EFI_DEVICE_PATH +#define efi_guid_t EFI_GUID +#endif /* _EFI_INCLUDE_ */ + +typedef struct { + uint8_t version; + uint8_t checksum; + uint8_t image_type; + uint8_t reserved; + uint32_t mode; + uint32_t x_offset; + uint32_t y_offset; +} ux_capsule_header_t; + +typedef struct update_info_s { + uint32_t update_info_version; + + /* stuff we need to apply an update */ + efi_guid_t guid; + uint32_t capsule_flags; + uint64_t hw_inst; + + EFI_TIME time_attempted; + + /* our metadata */ + uint32_t status; + + /* variadic device path */ + union { + efidp_header *dp_ptr; + efidp_header dp; + uint8_t dp_buf[0]; + }; +} __attribute__((__packed__)) update_info; + +#endif /* _FWUP_EFI_H */ diff --git a/plugins/uefi/efi/fwupdate.c b/plugins/uefi/efi/fwupdate.c new file mode 100644 index 000000000..8b01e0699 --- /dev/null +++ b/plugins/uefi/efi/fwupdate.c @@ -0,0 +1,1335 @@ +/* + * Copyright (C) 2014-2018 Red Hat, Inc. + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include +#include + +#include + +#include "fwup-efi.h" +#include "hexdump.h" + +#define UNUSED __attribute__((__unused__)) + +EFI_GUID empty_guid = {0x0,0x0,0x0,{0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0}}; +EFI_GUID fwupdate_guid = + {0x0abba7dc,0xe516,0x4167,{0xbb,0xf5,0x4d,0x9d,0x1c,0x73,0x94,0x16}}; +EFI_GUID ux_capsule_guid = + {0x3b8c8162,0x188c,0x46a4,{0xae,0xc9,0xbe,0x43,0xf1,0xd6,0x56,0x97}}; +EFI_GUID fmp_capsule_guid = + {0x6dcbd5ed,0xe82d,0x4c44,{0xbd,0xa1,0x71,0x94,0x19,0x9a,0xd9,0x2a}}; +EFI_GUID global_variable_guid = EFI_GLOBAL_VARIABLE; + + +typedef struct update_table_s { + CHAR16 *name; + UINT32 attributes; + UINTN size; + update_info *info; +} update_table; + +static EFI_STATUS +delete_variable(CHAR16 *name, EFI_GUID guid, UINT32 attributes); +static EFI_STATUS +set_variable(CHAR16 *name, EFI_GUID guid, VOID *data, UINTN size, + UINT32 attrs); + +static int debugging; + +#define SECONDS 1000000 + +static VOID +msleep(unsigned long msecs) +{ + BS->Stall(msecs); +} + + +/* + * I'm not actually sure when these appear, but they're present in the + * version in front of me. + */ +#if defined(__GNUC__) && defined(__GNUC_MINOR__) +#if __GNUC__ >= 5 && __GNUC_MINOR__ >= 1 +#define uintn_mult(a, b, c) __builtin_mul_overflow(a, b, c) +#endif +#endif +#ifndef uintn_mult +#define uintn_mult(a, b, c) ({ \ + const UINTN _limit = ~0UL; \ + int _ret = 1; \ + if ((a) != 0 && (b) != 0) { \ + _ret = _limit / (a) < (b); \ + } \ + if (!_ret) \ + *(c) = ((a) * (b)); \ + _ret; \ + }) +#endif + +int +debug_print(const char *func, const char *file, const int line, + CHAR16 *fmt, ...) +{ + va_list args0, args1; + CHAR16 *out0, *out1; + UINT32 attrs = EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS; + CHAR16 *name = L"FWUPDATE_DEBUG_LOG"; + static bool once = true; + + va_start(args0, fmt); + out0 = VPoolPrint(fmt, args0); + va_end(args0); + if (!out0) { + if (debugging) { + va_start(args1, fmt); + VPrint(fmt, args1); + va_end(args1); + msleep(200000); + } + Print(L"fwupdate: Allocation for debug log failed!\n"); + return debugging; + } + if (debugging) + Print(L"%s", out0); + out1 = PoolPrint(L"%a:%d:%a(): %s", file, line, func, out0); + FreePool(out0); + if (!out1) { + Print(L"fwupdate: Allocation for debug log failed!\n"); + return debugging; + } + + if (once) { + once = false; + delete_variable(name, fwupdate_guid, attrs); + } else { + attrs |= EFI_VARIABLE_APPEND_WRITE; + } + set_variable(name, fwupdate_guid, out1, StrSize(out1) - sizeof (CHAR16), attrs); + + FreePool(out1); + + return debugging; +} + +#define dprint(fmt, args...) debug_print(__func__, __FILE__, __LINE__, fmt, ## args ) +#define print(fmt, args...) ({ if (!dprint(fmt, ## args)) Print(fmt, ## args); }) + +/* + * Allocate some raw pages that aren't part of the pool allocator. + */ +static EFI_STATUS +allocate(void **addr, UINTN size) +{ + /* + * We're actually guaranteed that page size is 4096 by UEFI. + */ + UINTN pages = size / 4096 + ((size % 4096) ? 1 : 0); + EFI_STATUS rc; + EFI_PHYSICAL_ADDRESS pageaddr = 0; + EFI_ALLOCATE_TYPE type = AllocateAnyPages; + + if (sizeof (VOID *) == 4) { + pageaddr = 0xffffffffULL - 8192; + type = AllocateMaxAddress; + } + + rc = uefi_call_wrapper(BS->AllocatePages, 4, type, + EfiLoaderData, pages, + &pageaddr); + if (EFI_ERROR(rc)) + return rc; + if (sizeof (VOID *) == 4 && pageaddr > 0xffffffffULL) { + uefi_call_wrapper(BS->FreePages, 2, pageaddr, pages); + print(L"Got bad allocation at 0x%016x\n", (UINT64)pageaddr); + return EFI_OUT_OF_RESOURCES; + } + *addr = (void *)(UINTN)pageaddr; + return rc; +} + +/* + * Free our raw page allocations. + */ +static EFI_STATUS +free(void *addr, UINTN size) +{ + UINTN pages = size / 4096 + ((size % 4096) ? 1 : 0); + EFI_STATUS rc; + + rc = uefi_call_wrapper(BS->FreePages, 2, + (EFI_PHYSICAL_ADDRESS)(UINTN)addr, + pages); + return rc; +} + +static inline int +guid_cmp(efi_guid_t *a, efi_guid_t *b) +{ + return CompareMem(a, b, sizeof (*a)); +} + +EFI_STATUS +read_file(EFI_FILE_HANDLE fh, UINT8 **buf_out, UINTN *buf_size_out) +{ + UINT8 *b = NULL; + const UINTN bs = 512; + UINTN n_blocks = 4096; + UINTN i = 0; + EFI_STATUS rc; + + while (1) { + void *newb = NULL; + UINTN news = 0; + if (uintn_mult(bs * 2, n_blocks, &news)) { + if (b) + free(b, bs * n_blocks); + print(L"allocation %d * %d would overflow size\n", + bs * 2, n_blocks); + return EFI_OUT_OF_RESOURCES; + } + rc = allocate(&newb, news); + if (EFI_ERROR(rc)) { + print(L"Tried to allocate %d\n", + bs * n_blocks * 2); + print(L"Could not allocate memory.\n"); + return EFI_OUT_OF_RESOURCES; + } + if (b) { + CopyMem(newb, b, bs * n_blocks); + free(b, bs * n_blocks); + } + b = newb; + n_blocks *= 2; + + for (; i < n_blocks; i++) { + EFI_STATUS rc; + UINTN sz = bs; + + rc = uefi_call_wrapper(fh->Read, 3, fh, &sz, + &b[i * bs]); + if (EFI_ERROR(rc)) { + free(b, bs * n_blocks); + print(L"Could not read file: %r\n", rc); + return rc; + } + + if (sz != bs) { + *buf_size_out = bs * i + sz; + *buf_out = b; + return EFI_SUCCESS; + } + } + } + return EFI_SUCCESS; +} + +static EFI_STATUS +delete_variable(CHAR16 *name, EFI_GUID guid, UINT32 attributes) +{ + return uefi_call_wrapper(RT->SetVariable, 5, name, &guid, attributes, + 0, NULL); +} + +static EFI_STATUS +set_variable(CHAR16 *name, EFI_GUID guid, VOID *data, UINTN size, + UINT32 attrs) +{ + return uefi_call_wrapper(RT->SetVariable, 5, name, &guid, attrs, + size, data); +} + +static EFI_STATUS +read_variable(CHAR16 *name, EFI_GUID guid, void **buf_out, UINTN *buf_size_out, + UINT32 *attributes_out) +{ + EFI_STATUS rc; + UINT32 attributes; + UINTN size = 0; + void *buf = NULL; + + rc = uefi_call_wrapper(RT->GetVariable, 5, name, + &guid, &attributes, &size, NULL); + if (EFI_ERROR(rc)) { + if (rc == EFI_BUFFER_TOO_SMALL) { + buf = AllocatePool(size); + if (!buf) { + print(L"Tried to allocate %d\n", size); + print(L"Could not allocate memory.\n"); + return EFI_OUT_OF_RESOURCES; + } + } else if (rc != EFI_NOT_FOUND) { + print(L"Could not get variable \"%s\": %r\n", name, rc); + return rc; + } + } else { + print(L"GetVariable(%s) succeeded with size=0.\n", name); + return EFI_INVALID_PARAMETER; + } + rc = uefi_call_wrapper(RT->GetVariable, 5, name, &guid, &attributes, + &size, buf); + if (EFI_ERROR(rc)) { + print(L"Could not get variable \"%s\": %r\n", name, rc); + FreePool(buf); + return rc; + } + *buf_out = buf; + *buf_size_out = size; + *attributes_out = attributes; + return EFI_SUCCESS; +} + +static INTN +dp_size(EFI_DEVICE_PATH *dp, INTN limit) +{ + INTN ret = 0; + while (1) { + if (limit < 4) + break; + INTN nodelen = DevicePathNodeLength(dp); + if (nodelen > limit) + break; + limit -= nodelen; + ret += nodelen; + + if (IsDevicePathEnd(dp)) + return ret; + dp = NextDevicePathNode(dp); + } + return -1; +} + +static EFI_STATUS +get_info(CHAR16 *name, update_table *info_out) +{ + EFI_STATUS rc; + update_info *info = NULL; + UINTN info_size = 0; + UINT32 attributes = 0; + void *info_ptr = NULL; + + rc = read_variable(name, fwupdate_guid, &info_ptr, &info_size, + &attributes); + if (EFI_ERROR(rc)) + return rc; + info = (update_info *)info_ptr; + + if (info_size < sizeof (*info)) { + print(L"Update \"%s\" is is too small.\n", name); + delete_variable(name, fwupdate_guid, attributes); + return EFI_INVALID_PARAMETER; + } + + if (info_size - sizeof (EFI_DEVICE_PATH) <= sizeof (*info)) { + print(L"Update \"%s\" is malformed, " + L"and cannot hold a file path.\n", name); + delete_variable(name, fwupdate_guid, attributes); + return EFI_INVALID_PARAMETER; + } + + EFI_DEVICE_PATH *hdr = (EFI_DEVICE_PATH *)&info->dp; + INTN is = EFI_FIELD_OFFSET(update_info, dp); + if (is > (INTN)info_size) { + print(L"Update \"%s\" has an invalid file path.\n" + L"Device path offset is %d, but total size is %d\n", + name, is, info_size); + delete_variable(name, fwupdate_guid, attributes); + return EFI_INVALID_PARAMETER; + } + + is = info_size - is; + INTN sz = dp_size(hdr, info_size); + if (sz < 0 || is < 0) { +invalid_size: + print(L"Update \"%s\" has an invalid file path.\n" + L"update info size: %d dp size: %d size for dp: %d\n", + name, info_size, sz, is); + delete_variable(name, fwupdate_guid, attributes); + return EFI_INVALID_PARAMETER; + } + if (is > (INTN)info_size) + goto invalid_size; + if (is != sz) + goto invalid_size; + + info_out->info = info; + info_out->size = info_size; + info_out->attributes = attributes; + + return EFI_SUCCESS; +} + +static EFI_STATUS +find_updates(UINTN *n_updates_out, update_table ***updates_out) +{ + EFI_STATUS rc; + update_table **updates = NULL; + UINTN n_updates = 0; + UINTN n_updates_allocated = 128; + EFI_STATUS ret = EFI_OUT_OF_RESOURCES; + +#define GNVN_BUF_SIZE 1024 + UINTN variable_name_allocation = GNVN_BUF_SIZE; + UINTN variable_name_size = 0; + CHAR16 *variable_name; + EFI_GUID vendor_guid = empty_guid; + UINTN mult_res; + + if (uintn_mult(sizeof (update_table *), n_updates_allocated, + &mult_res)) { + print(L"Allocation %d * %d would overflow size\n", + sizeof (update_table *), n_updates_allocated); + return EFI_OUT_OF_RESOURCES; + } + + updates = AllocateZeroPool(mult_res); + if (!updates) { + print(L"Tried to allocate %d\n", mult_res); + print(L"Could not allocate memory.\n"); + return EFI_OUT_OF_RESOURCES; + } + + /* How much do we trust "size of the VariableName buffer" to mean + * sizeof(vn) and not sizeof(vn)/sizeof(vn[0]) ? */ + variable_name = AllocateZeroPool(GNVN_BUF_SIZE * 2); + if (!variable_name) { + print(L"Tried to allocate %d\n", GNVN_BUF_SIZE * 2); + print(L"Could not allocate memory.\n"); + FreePool(updates); + return EFI_OUT_OF_RESOURCES; + } + + while (1) { + variable_name_size = variable_name_allocation; + rc = uefi_call_wrapper(RT->GetNextVariableName, 3, + &variable_name_size, variable_name, + &vendor_guid); + if (rc == EFI_BUFFER_TOO_SMALL) { + /* If we don't have a big enough buffer to hold the + * name, allocate a bigger one and try again */ + UINTN new_allocation; + CHAR16 *new_name; + + new_allocation = variable_name_size; + if (uintn_mult(new_allocation, 2, &mult_res)) { + print(L"%d * 2 would overflow size\n", + new_allocation); + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + new_name = AllocatePool(new_allocation * 2); + if (!new_name) { + print(L"Tried to allocate %d\n", + new_allocation * 2); + print(L"Could not allocate memory.\n"); + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + CopyMem(new_name, variable_name, + variable_name_allocation); + variable_name_allocation = new_allocation; + FreePool(variable_name); + variable_name = new_name; + continue; + } else if (rc == EFI_NOT_FOUND) { + break; + } else if (EFI_ERROR(rc)) { + print(L"Could not get variable name: %r\n", rc); + ret = rc; + goto err; + } + + /* + * If it's not one of our state variables, keep going. + */ + if (guid_cmp(&vendor_guid, &fwupdate_guid)) + continue; + + /* + * Don't delete our debugging settings. + */ + if (StrCmp(variable_name, L"FWUPDATE_VERBOSE") == 0 || + StrCmp(variable_name, L"FWUPDATE_DEBUG_LOG") == 0) + continue; + + UINTN vns = StrLen(variable_name); + CHAR16 vn[vns + 1]; + CopyMem(vn, variable_name, vns * sizeof (vn[0])); + vn[vns] = L'\0'; + print(L"Found update %s\n", vn); + + if (n_updates == n_updates_allocated) { + update_table **new_ups; + UINTN mul_a, mul_b; + if (uintn_mult(n_updates_allocated, 2, &mult_res)) { + mul_a = n_updates_allocated; + mul_b = 2; +mult_err: + print(L"Allocation %d * %d would overflow size\n", + mul_a, mul_b); + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + if (uintn_mult(mult_res, sizeof (update_table *), + &mult_res)) { + mul_a = mult_res; + mul_b = sizeof (update_table *); + goto mult_err; + } + + new_ups = AllocateZeroPool(mult_res); + if (!new_ups) { + print(L"Tried to allocate %d\n", mult_res); + print(L"Could not allocate memory.\n"); + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + CopyMem(new_ups, updates, mult_res); + n_updates_allocated *= 2; + FreePool(updates); + updates = new_ups; + } + + update_table *update = AllocatePool(sizeof (update_table)); + if (!update) { + print(L"Tried to allocate %d\n", sizeof (update_table)); + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + + update->name = StrDuplicate(vn); + if (!update->name) { + print(L"Tried to allocate %d\n", StrSize(vn)); + ret = EFI_OUT_OF_RESOURCES; + FreePool(update); + goto err; + } + + rc = get_info(vn, update); + if (EFI_ERROR(rc)) { + print(L"Could not get update info for \"%s\", aborting.\n", vn); + ret = rc; + FreePool(update->name); + FreePool(update); + goto err; + } + if (update->info->status & FWUPDATE_ATTEMPT_UPDATE) { + EFI_TIME_CAPABILITIES timecaps = { 0, }; + + uefi_call_wrapper(RT->GetTime, 2, + &update->info->time_attempted, + &timecaps); + update->info->status = FWUPDATE_ATTEMPTED; + updates[n_updates++] = update; + } else { + FreePool(update->info); + FreePool(update->name); + FreePool(update); + } + } + + FreePool(variable_name); + + *n_updates_out = n_updates; + *updates_out = updates; + + return EFI_SUCCESS; +err: + FreePool(variable_name); + + for (unsigned int i = 0; i < n_updates; i++) { + FreePool(updates[i]->name); + FreePool(updates[i]->info); + FreePool(updates[i]); + } + + FreePool(updates); + return ret; +} + +static EFI_STATUS +search_file(EFI_DEVICE_PATH **file_dp, EFI_FILE_HANDLE *fh) +{ + EFI_DEVICE_PATH *dp, *parent_dp; + EFI_GUID sfsp = SIMPLE_FILE_SYSTEM_PROTOCOL; + EFI_GUID dpp = DEVICE_PATH_PROTOCOL; + EFI_FILE_HANDLE *devices; + UINTN i, n_handles, count; + EFI_STATUS rc; + + rc = uefi_call_wrapper(BS->LocateHandleBuffer, 5, ByProtocol, &sfsp, + NULL, &n_handles, (EFI_HANDLE **)&devices); + if (EFI_ERROR(rc)) { + print(L"Could not find handles.\n"); + return rc; + } + + dp = *file_dp; + + if (debugging) + print(L"Searching Device Path: %s ...\n", DevicePathToStr(dp)); + + parent_dp = DuplicateDevicePath(dp); + if (!parent_dp) { + rc = EFI_INVALID_PARAMETER; + goto out; + } + + dp = parent_dp; + count = 0; + while (1) { + if (IsDevicePathEnd(dp)) { + rc = EFI_INVALID_PARAMETER; + goto out; + } + + if (DevicePathType(dp) == MEDIA_DEVICE_PATH && + DevicePathSubType(dp) == MEDIA_FILEPATH_DP) + break; + + dp = NextDevicePathNode(dp); + ++count; + } + + SetDevicePathEndNode(dp); + + if (debugging) + print(L"Device Path prepared: %s\n", + DevicePathToStr(parent_dp)); + + for (i = 0; i < n_handles; i++) { + EFI_DEVICE_PATH *path; + + rc = uefi_call_wrapper(BS->HandleProtocol, 3, devices[i], &dpp, + (void **)&path); + if (EFI_ERROR(rc)) + continue; + + if (debugging) + print(L"Device supporting SFSP: %s\n", + DevicePathToStr(path)); + + rc = EFI_UNSUPPORTED; + while (!IsDevicePathEnd(path)) { + if (debugging) + print(L"Comparing: %s and %s\n", + DevicePathToStr(parent_dp), + DevicePathToStr(path)); + + if (LibMatchDevicePaths(path, parent_dp) == TRUE) { + *fh = devices[i]; + for (i = 0; i < count; i++) + *file_dp = NextDevicePathNode(*file_dp); + rc = EFI_SUCCESS; + + if (debugging) + print(L"Match up! Returning %s\n", + DevicePathToStr(*file_dp)); + + goto out; + } + + path = NextDevicePathNode(path); + } + } + +out: + if (!EFI_ERROR(rc)) + print(L"File %s searched\n", DevicePathToStr(*file_dp)); + + uefi_call_wrapper(BS->FreePool, 1, devices); + return rc; +} + +static EFI_STATUS +open_file(EFI_DEVICE_PATH *dp, EFI_FILE_HANDLE *fh) +{ + EFI_DEVICE_PATH *file_dp = dp; + EFI_GUID sfsp = SIMPLE_FILE_SYSTEM_PROTOCOL; + EFI_FILE_HANDLE device; + EFI_FILE_IO_INTERFACE *drive; + EFI_FILE_HANDLE root; + EFI_STATUS rc; + + rc = uefi_call_wrapper(BS->LocateDevicePath, 3, &sfsp, &file_dp, + (EFI_HANDLE *)&device); + if (EFI_ERROR(rc)) { + rc = search_file(&file_dp, &device); + if (EFI_ERROR(rc)) { + print(L"Could not locate device handle: %r\n", rc); + return rc; + } + } + + if (DevicePathType(file_dp) != MEDIA_DEVICE_PATH || + DevicePathSubType(file_dp) != MEDIA_FILEPATH_DP) { + print(L"Could not find appropriate device.\n"); + return EFI_UNSUPPORTED; + } + + UINT16 sz16; + UINTN sz; + CopyMem(&sz16, &file_dp->Length[0], sizeof(sz16)); + sz = sz16; + sz -= 4; + if (sz <= 6 || sz % 2 != 0) { + print(L"Invalid file device path.\n"); + return EFI_INVALID_PARAMETER; + } + + sz /= sizeof (CHAR16); + /* + * check against some arbitrary limit to avoid having a stack + * overflow here. + */ + if (sz > 1024) { + print(L"Invalid file device path.\n"); + return EFI_INVALID_PARAMETER; + } + CHAR16 filename[sz+1]; + CopyMem(filename, (UINT8 *)file_dp + 4, sz * sizeof (CHAR16)); + filename[sz] = L'\0'; + + rc = uefi_call_wrapper(BS->HandleProtocol, 3, device, &sfsp, + (void **)&drive); + if (EFI_ERROR(rc)) { + print(L"Could not open device interface: %r.\n", rc); + return rc; + } + dprint(L"Found device\n"); + + rc = uefi_call_wrapper(drive->OpenVolume, 2, drive, &root); + if (EFI_ERROR(rc)) { + print(L"Could not open volume: %r.\n", rc); + return rc; + } + dprint(L"Found volume\n"); + + rc = uefi_call_wrapper(root->Open, 5, root, fh, filename, + EFI_FILE_MODE_READ, 0); + if (EFI_ERROR(rc)) { + print(L"Could not open file \"%s\": %r.\n", filename, rc); + return rc; + } + dprint(L"Found file\n"); + + return EFI_SUCCESS; +} + +static EFI_STATUS +delete_boot_order(CHAR16 *name, EFI_GUID guid) +{ + + UINTN i; + UINT16 boot_num; + EFI_STATUS rc; + UINTN info_size = 0; + UINT32 attributes = 0; + void *info_ptr = NULL; + UINT16 *new_info_ptr = NULL; + BOOLEAN num_found = FALSE; + UINTN new_list_num = 0; + + /* get boot hex number */ + boot_num = xtoi((CHAR16 *)((UINT8 *)name + sizeof(L"Boot"))); + + rc = read_variable(L"BootOrder", guid, &info_ptr, &info_size, + &attributes); + if (EFI_ERROR(rc)) + return rc; + + new_info_ptr = AllocatePool(info_size); + if (!new_info_ptr) { + print(L"Tried to allocate %d\n", info_size); + print(L"Could not allocate memory.\n"); + FreePool(info_ptr); + return EFI_OUT_OF_RESOURCES; + } + + for (i = 0; i < (info_size / sizeof(UINT16)) ; i++) { + if (((UINT16 *)info_ptr)[i] != boot_num) { + new_info_ptr[i] = ((UINT16 *)info_ptr)[i]; + new_list_num++; + + } else { + num_found = TRUE; + } + } + + /* if not in the BootOrder list, do not update BootOrder */ + if (!num_found) { + rc = EFI_SUCCESS; + goto out; + } + + rc = uefi_call_wrapper(RT->SetVariable, 5, L"BootOrder", &guid, + attributes, new_list_num * sizeof(UINT16), + new_info_ptr); + if (EFI_ERROR(rc)) { + print(L"Could not update variable status for \"%s\": %r\n", + name, rc); + goto out; + } + +out: + + FreePool(info_ptr); + FreePool(new_info_ptr); + + return rc; +} + +static EFI_STATUS +delete_boot_entry(void) +{ + EFI_STATUS rc; + + UINTN variable_name_allocation = GNVN_BUF_SIZE; + UINTN variable_name_size = 0; + CHAR16 *variable_name; + EFI_GUID vendor_guid = empty_guid; + UINTN mult_res; + EFI_STATUS ret = EFI_OUT_OF_RESOURCES; + + variable_name = AllocateZeroPool(GNVN_BUF_SIZE * 2); + if (!variable_name) { + print(L"Tried to allocate %d\n", GNVN_BUF_SIZE * 2); + print(L"Could not allocate memory.\n"); + return EFI_OUT_OF_RESOURCES; + } + + while (1) { + variable_name_size = variable_name_allocation; + rc = uefi_call_wrapper(RT->GetNextVariableName, 3, + &variable_name_size, variable_name, + &vendor_guid); + if (rc == EFI_BUFFER_TOO_SMALL) { + + UINTN new_allocation; + CHAR16 *new_name; + + new_allocation = variable_name_size; + if (uintn_mult(new_allocation, 2, &mult_res)) { + print(L"%d * 2 would overflow size\n", + new_allocation); + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + new_name = AllocatePool(new_allocation * 2); + if (!new_name) { + print(L"Tried to allocate %d\n", + new_allocation * 2); + print(L"Could not allocate memory.\n"); + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + CopyMem(new_name, variable_name, + variable_name_allocation); + variable_name_allocation = new_allocation; + FreePool(variable_name); + variable_name = new_name; + continue; + } else if (rc == EFI_NOT_FOUND) { + break; + } else if (EFI_ERROR(rc)) { + print(L"Could not get variable name: %r\n", rc); + ret = rc; + goto err; + } + + /* check if the variable name is Boot#### */ + UINTN vns = StrLen(variable_name); + if (!guid_cmp(&vendor_guid, &global_variable_guid) + && vns == 8 && CompareMem(variable_name, L"Boot", 8) == 0) { + UINTN info_size = 0; + UINT32 attributes = 0; + void *info_ptr = NULL; + CHAR16 *load_op_description = NULL; + CHAR16 target[] = L"Linux-Firmware-Updater"; + + rc = read_variable(variable_name, vendor_guid, + &info_ptr, &info_size, &attributes); + if (EFI_ERROR(rc)) { + ret = rc; + goto err; + } + + /* + * check if the boot path created by fwupdate, + * check with EFI_LOAD_OPTION decription + */ + load_op_description = (CHAR16 *) + ((UINT8 *)info_ptr + + sizeof(UINT32) + + sizeof(UINT16)); + + if (CompareMem(load_op_description, target, + sizeof(target) - 2) == 0) { + /* delete the boot path from BootOrder list */ + rc = delete_boot_order(variable_name, + vendor_guid); + if (EFI_ERROR(rc)) { + print(L"Failed to delete the Linux-Firmware-Updater boot entry from BootOrder.\n"); + FreePool(info_ptr); + ret = rc; + goto err; + } + + rc = delete_variable(variable_name, + vendor_guid, attributes); + if (EFI_ERROR(rc)) { + print(L"Failed to delete the Linux-Firmware-Updater boot entry.\n"); + FreePool(info_ptr); + ret = rc; + goto err; + } + + FreePool(info_ptr); + break; + } + + FreePool(info_ptr); + } + } + + ret = EFI_SUCCESS; +err: + FreePool(variable_name); + return ret; +} + +static EFI_STATUS +get_gop_mode(UINT32 *mode, EFI_HANDLE loaded_image) +{ + EFI_HANDLE *handles, gop_handle; + UINTN num_handles, i; + EFI_STATUS status; + EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + EFI_GRAPHICS_OUTPUT_PROTOCOL *gop; + void *iface; + + status = LibLocateHandle(ByProtocol, &gop_guid, NULL, &num_handles, + &handles); + if (EFI_ERROR(status)) + return status; + + if (!handles || num_handles == 0) + return EFI_UNSUPPORTED; + + for (i = 0; i < num_handles; i++) { + gop_handle = handles[i]; + + status = uefi_call_wrapper(BS->OpenProtocol, 6, + gop_handle, &gop_guid, &iface, + loaded_image, 0, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(status)) + continue; + + gop = (EFI_GRAPHICS_OUTPUT_PROTOCOL *)iface; + + *mode = gop->Mode->Mode; + return EFI_SUCCESS; + } + + return EFI_UNSUPPORTED; +} + +static UINT32 csum(UINT8 *buf, UINTN size) +{ + UINT32 sum = 0; + UINTN i; + + dprint(L"checksumming %d bytes at 0x%08x\n", size, buf); + + for (i = 0; i < size; i++) { + sum += buf[i]; + if (debugging) + Print(L"\rpos:%08lx csum:%d", buf+i, sum); + } + if (debugging) + Print(L"\n"); + + return sum; +} + +static EFI_STATUS +do_ux_csum(EFI_HANDLE loaded_image, UINT8 *buf, UINTN size) +{ + ux_capsule_header_t *payload_hdr; + EFI_CAPSULE_HEADER *capsule; + EFI_STATUS rc; + UINTN sum = 0; + + UINT8 *current = buf; + UINTN left = size; + + if (size < sizeof(*capsule)) { + dprint(L"Invalid capsule size %d\n", size); + return EFI_INVALID_PARAMETER; + } + + capsule = (EFI_CAPSULE_HEADER *)buf; + + if (debugging) + hexdump(buf, size <= 0x40 ? size : 0x40); + + dprint(L"size: %d\n", size); + dprint(L"&HeaderSize: 0x%08lx\n", &capsule->HeaderSize); + dprint(L"HeaderSize: %d\n", capsule->HeaderSize); + dprint(L"&CapsuleImageSize: 0x%08lx\n", &capsule->CapsuleImageSize); + dprint(L"CapsuleImageSize: %d\n", capsule->CapsuleImageSize); + + if (size < capsule->HeaderSize) { + dprint(L"Invalid capsule header size %d\n", size); + return EFI_INVALID_PARAMETER; + } + + sum += csum(current, capsule->HeaderSize); + current += capsule->HeaderSize; + left -= capsule->HeaderSize; + + payload_hdr = (ux_capsule_header_t *)(buf) + capsule->HeaderSize; + dprint(L"&PayloadHeader: 0x%08lx\n", payload_hdr); + dprint(L"PayloadHeader Size: %d\n", sizeof (*payload_hdr)); + rc = get_gop_mode(&payload_hdr->mode, loaded_image); + if (EFI_ERROR(rc)) + return EFI_UNSUPPORTED; + + payload_hdr->checksum = 0; + sum += csum(current, sizeof(*payload_hdr)); + current += sizeof(*payload_hdr); + left -= sizeof(*payload_hdr); + + sum += csum(current, left); + dprint(L"sum is 0x%02hhx; setting ->checksum to 0x%02hhx\n", + sum & 0xff, (uint8_t)((int8_t)(0 - (sum & 0xff)))); + + payload_hdr->checksum = (uint8_t)((int8_t)(0 - sum)); + dprint(L"checksum is set...\n"); + + return EFI_SUCCESS; +} + +#define is_ux_capsule(guid) (guid_cmp(guid, &ux_capsule_guid) == 0) +#define is_fmp_capsule(guid) (guid_cmp(guid, &fmp_capsule_guid) == 0) + +static EFI_STATUS +add_capsule(update_table *update, EFI_CAPSULE_HEADER **capsule_out, + EFI_CAPSULE_BLOCK_DESCRIPTOR *cbd_out, EFI_HANDLE loaded_image) +{ + EFI_STATUS rc; + EFI_FILE_HANDLE fh = NULL; + UINT8 *fbuf = NULL; + UINTN fsize = 0; + EFI_CAPSULE_HEADER *capsule; + + UINTN cbd_len; + EFI_PHYSICAL_ADDRESS cbd_data; + EFI_CAPSULE_HEADER *cap_out; + + rc = open_file((EFI_DEVICE_PATH *)update->info->dp_buf, &fh); + if (EFI_ERROR(rc)) + return rc; + + rc = read_file(fh, &fbuf, &fsize); + if (EFI_ERROR(rc)) + return rc; + + uefi_call_wrapper(fh->Close, 1, fh); + + dprint(L"Read file; %d bytes\n", fsize); + dprint(L"updates guid: %g\n", &update->info->guid); + dprint(L"File guid: %g\n", fbuf); + + /* + * See if it has the capsule header, and if not, add one. + * + * Unfortunately there's not a good way to do this, so we're just + * checking if the capsule has the fw_class guid at the right place. + */ + if ((guid_cmp(&update->info->guid, (efi_guid_t *)fbuf) == 0 || + is_fmp_capsule((efi_guid_t *)fbuf)) && + /* + * We're ignoring things that are 40 bytes here, because that's + * the size of the variables used in the test code I wrote for + * edk2 - It's basically a capsule header with no payload, so + * there's nothing real it can do anyway. + * + * At some point I'll update that to be slightly different and + * take the exception out, but it's not pressing. + */ + fsize != 40) { + dprint(L"Image has capsule image embedded\n"); + cbd_len = fsize; + cbd_data = (EFI_PHYSICAL_ADDRESS)(UINTN)fbuf; + capsule = cap_out = (EFI_CAPSULE_HEADER *)fbuf; + if (!cap_out->Flags && !is_ux_capsule(&update->info->guid)) { +#if defined(__aarch64__) + cap_out->Flags |= update->info->capsule_flags; +#else + cap_out->Flags |= update->info->capsule_flags | + CAPSULE_FLAGS_PERSIST_ACROSS_RESET | + CAPSULE_FLAGS_INITIATE_RESET; +#endif + } + } else { + dprint(L"Image does not have embedded header\n"); + dprint(L"Allocating %d for capsule header.\n", + sizeof (*capsule)+fsize); + rc = allocate((void **)&capsule, sizeof (*capsule) + fsize); + if (EFI_ERROR(rc)) { + print(L"Tried to allocate %d\n", + sizeof (*capsule) + fsize); + print(L"Could not allocate space for update: %r.\n", + rc); + return EFI_OUT_OF_RESOURCES; + } + capsule->CapsuleGuid = update->info->guid; + capsule->HeaderSize = sizeof (*capsule); + if (!is_ux_capsule(&update->info->guid)) { +#if defined(__aarch64__) + capsule->Flags |= update->info->capsule_flags; +#else + capsule->Flags = update->info->capsule_flags | + CAPSULE_FLAGS_PERSIST_ACROSS_RESET | + CAPSULE_FLAGS_INITIATE_RESET; +#endif + } + capsule->CapsuleImageSize = fsize + sizeof (*capsule); + + UINT8 *buffer = (UINT8 *)capsule + capsule->HeaderSize; + CopyMem(buffer, fbuf, fsize); + cbd_len = capsule->CapsuleImageSize; + cbd_data = (EFI_PHYSICAL_ADDRESS)(UINTN)capsule; + cap_out = capsule; + free(fbuf, fsize); + } + + if (is_ux_capsule(&update->info->guid)) { + dprint(L"Checksumming ux capsule\n"); + rc = do_ux_csum(loaded_image, (UINT8 *)capsule, cbd_len); + if (EFI_ERROR(rc)) + return EFI_UNSUPPORTED; + } + + cbd_out->Length = cbd_len; + cbd_out->Union.DataBlock = cbd_data; + *capsule_out = cap_out; + + return EFI_SUCCESS; +} + + +static EFI_STATUS +apply_capsules(EFI_CAPSULE_HEADER **capsules, + EFI_CAPSULE_BLOCK_DESCRIPTOR *cbd, + UINTN num_updates, EFI_RESET_TYPE *reset) +{ + UINT64 max_capsule_size; + EFI_STATUS rc; + + rc = delete_boot_entry(); + if (EFI_ERROR(rc)) { + /* + * Print out deleting boot entry error, but still try to apply + * capsule. + */ + dprint(L"Could not delete boot entry: %r\n", + __FILE__, __func__, __LINE__, rc); + } + + rc = uefi_call_wrapper(RT->QueryCapsuleCapabilities, 4, capsules, + num_updates, &max_capsule_size, reset); + dprint(L"QueryCapsuleCapabilities: %r max: %ld reset:%d\n", + rc, max_capsule_size, *reset); + dprint(L"Capsules: %d\n", num_updates); + + uefi_call_wrapper(BS->Stall, 1, 1 * SECONDS); + rc = uefi_call_wrapper(RT->UpdateCapsule, 3, capsules, num_updates, + (EFI_PHYSICAL_ADDRESS)(UINTN)cbd); + if (EFI_ERROR(rc)) { + print(L"Could not apply capsule update: %r\n", rc); + return rc; + } + + return EFI_SUCCESS; +} + +static +EFI_STATUS +set_statuses(UINTN n_updates, update_table **updates) +{ + EFI_STATUS rc; + for (UINTN i = 0; i < n_updates; i++) { + rc = uefi_call_wrapper(RT->SetVariable, 5, updates[i]->name, + &fwupdate_guid, updates[i]->attributes, + updates[i]->size, updates[i]->info); + if (EFI_ERROR(rc)) { + print(L"Coould not update variable status for \"%s\": %r\n", + updates[i]->name, rc); + return rc; + } + } + return EFI_SUCCESS; +} + +EFI_GUID SHIM_LOCK_GUID = + {0x605dab50,0xe046,0x4300,{0xab,0xb6,0x3d,0xd8,0x10,0xdd,0x8b,0x23}}; + +static void +__attribute__((__optimize__("0"))) +debug_hook(void) +{ + EFI_GUID guid = SHIM_LOCK_GUID; + UINTN data = 0; + UINTN data_size = 1; + EFI_STATUS efi_status; + UINT32 attributes; + register volatile int x = 0; + extern char _text UNUSED, _data UNUSED; + + /* + * If SHIM_DEBUG is set, we're going to assume shim has done whatever + * is needed to get a debugger attached, and we just need to explain + * who and where we are, and also enable our debugging output. + */ + efi_status = uefi_call_wrapper(RT->GetVariable, 5, L"SHIM_DEBUG", + &guid, &attributes, &data_size, &data); + if (EFI_ERROR(efi_status) || data != 1) { + efi_status = uefi_call_wrapper(RT->GetVariable, 5, + L"FWUPDATE_VERBOSE", + &fwupdate_guid, &attributes, + &data_size, &data); + if (EFI_ERROR(efi_status) || data != 1) { + return; + } + debugging = 1; + return; + } + + debugging = 1; + if (x) + return; + + x = 1; + print(L"add-symbol-file "DEBUGDIR + L"fwupdate.efi.debug %p -s .data %p\n", + &_text, &_data); +} + +EFI_STATUS +efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) +{ + EFI_STATUS rc; + update_table **updates = NULL; + UINTN n_updates = 0; + EFI_RESET_TYPE reset_type = EfiResetWarm; + + InitializeLib(image, systab); + + /* + * if SHIM_DEBUG is set, print info for our attached debugger. + */ + debug_hook(); + + /* + * Basically the workflow here is: + * 1) find and validate any update state variables with the right GUID + * 2) allocate our capsule data structures and add the capsules + * #1 described + * 3) update status variables + * 4) apply the capsule updates + * 5) reboot + */ + + /* + * Step 1: find and validate update state variables + */ + /* XXX TODO: + * 1) survey the reset types first, and separate into groups + * according to them + * 2) if there's more than one, mirror BootCurrent back into BootNext + * so we can do multiple runs + * 3) only select the ones from one type for the first go + */ + rc = find_updates(&n_updates, &updates); + if (EFI_ERROR(rc)) { + print(L"fwupdate: Could not find updates: %r\n", rc); + return rc; + } + if (n_updates == 0) { + print(L"fwupdate: No updates to process. Called in error?\n"); + return EFI_INVALID_PARAMETER; + } + + /* + * Step 2: Build our data structure and add the capsules to it. + */ + EFI_CAPSULE_HEADER *capsules[n_updates + 1]; + EFI_CAPSULE_BLOCK_DESCRIPTOR *cbd_data; + UINTN i, j; + rc = allocate((void **)&cbd_data, + sizeof (EFI_CAPSULE_BLOCK_DESCRIPTOR)*(n_updates+1)); + if (EFI_ERROR(rc)) { + print(L"Tried to allocate %d\n", + sizeof (EFI_CAPSULE_BLOCK_DESCRIPTOR)*(n_updates+1)); + print(L"fwupdate: Could not allocate memory: %r.\n",rc); + return rc; + } + for (i = 0, j = 0; i < n_updates; i++) { + dprint(L"Adding new capsule\n"); + rc = add_capsule(updates[i], &capsules[j], &cbd_data[j], + image); + if (EFI_ERROR(rc)) { + if (rc == EFI_UNSUPPORTED && + is_ux_capsule(&updates[i]->info->guid)) + continue; + dprint(L"fwupdate: Could not build update list: %r\n", + rc); + return rc; + } + j++; + } + n_updates = j; + dprint(L"n_updates: %d\n", n_updates); + + cbd_data[i].Length = 0; + cbd_data[i].Union.ContinuationPointer = 0; + + /* + * Step 3: update the state variables. + */ + rc = set_statuses(n_updates, updates); + if (EFI_ERROR(rc)) { + print(L"fwupdate: Could not set update status: %r\n", rc); + return rc; + } + + /* + * Step 4: apply the capsules. + */ + rc = apply_capsules(capsules, cbd_data, n_updates, &reset_type); + if (EFI_ERROR(rc)) { + print(L"fwupdate: Could not apply capsules: %r\n", rc); + return rc; + } + + /* + * Step 5: if #4 didn't reboot us, do it manually. + */ + dprint(L"fwupdate: Reset System\n"); + if (debugging) + uefi_call_wrapper(BS->Stall, 1, 5 * SECONDS); + + uefi_call_wrapper(BS->Stall, 1, 5 * SECONDS); + uefi_call_wrapper(RT->ResetSystem, 4, reset_type, EFI_SUCCESS, + 0, NULL); + + return EFI_SUCCESS; +} diff --git a/plugins/uefi/efi/hexdump.h b/plugins/uefi/efi/hexdump.h new file mode 100644 index 000000000..7a96b3851 --- /dev/null +++ b/plugins/uefi/efi/hexdump.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015-2016 Peter Jones + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef STATIC_HEXDUMP_H +#define STATIC_HEXDUMP_H + +static int +__attribute__((__unused__)) +isprint(char c) +{ + if (c < 0x20) + return 0; + if (c > 0x7e) + return 0; + return 1; +} + +static UINTN +__attribute__((__unused__)) +format_hex(UINT8 *data, UINTN size, CHAR16 *buf) +{ + UINTN sz = (UINTN)data % 16; + CHAR16 hexchars[] = L"0123456789abcdef"; + int offset = 0; + unsigned int i; + unsigned int j; + + for (i = 0; i < sz; i++) { + buf[offset++] = L' '; + buf[offset++] = L' '; + buf[offset++] = L' '; + if (i == 7) + buf[offset++] = L' '; + } + for (j = sz; j < 16 && j < size; j++) { + UINT8 d = data[j-sz]; + buf[offset++] = hexchars[(d & 0xf0) >> 4]; + buf[offset++] = hexchars[(d & 0x0f)]; + if (j != 15) + buf[offset++] = L' '; + if (j == 7) + buf[offset++] = L' '; + } + for (i = j; i < 16; i++) { + buf[offset++] = L' '; + buf[offset++] = L' '; + if (i != 15) + buf[offset++] = L' '; + if (i == 7) + buf[offset++] = L' '; + } + buf[offset] = L'\0'; + return j - sz; +} + +static void +__attribute__((__unused__)) +format_text(UINT8 *data, UINTN size, CHAR16 *buf) +{ + UINTN sz = (UINTN)data % 16; + int offset = 0; + unsigned int i; + unsigned int j; + + for (i = 0; i < sz; i++) + buf[offset++] = L' '; + buf[offset++] = L'|'; + for (j = sz; j < 16 && j < size; j++) { + if (isprint(data[j-sz])) + buf[offset++] = data[j-sz]; + else + buf[offset++] = L'.'; + } + buf[offset++] = L'|'; + for (i = j; i < 16; i++) + buf[offset++] = L' '; + buf[offset] = L'\0'; +} + +static void +__attribute__((__unused__)) +hexdump(UINT8 *data, UINTN size) +{ + UINTN display_offset = (UINTN)data & 0xffffffff; + UINTN offset = 0; + //Print(L"hexdump: data=0x%016x size=0x%x\n", data, size); + + while (offset < size) { + CHAR16 hexbuf[49]; + CHAR16 txtbuf[19]; + UINTN sz; + + sz = format_hex(data+offset, size-offset, hexbuf); + if (sz == 0) + return; + uefi_call_wrapper(BS->Stall, 1, 200000); + + format_text(data+offset, size-offset, txtbuf); + Print(L"%08x %s %s\n", display_offset, hexbuf, txtbuf); + uefi_call_wrapper(BS->Stall, 1, 200000); + + display_offset += sz; + offset += sz; + } +} + + +#endif diff --git a/plugins/uefi/efi/meson.build b/plugins/uefi/efi/meson.build new file mode 100644 index 000000000..6316dd692 --- /dev/null +++ b/plugins/uefi/efi/meson.build @@ -0,0 +1,155 @@ +efi_cc = get_option('efi-cc') +efi_ld = get_option('efi-ld') +efi_ldsdir = get_option('efi-ldsdir') +efi_incdir = get_option('efi-includedir') + +gnu_efi_path_arch = '' +foreach name : [gnu_efi_arch, EFI_MACHINE_TYPE_NAME] + if (gnu_efi_path_arch == '' and name != '' and + cc.has_header('@0@/@1@/efibind.h'.format(efi_incdir, name))) + gnu_efi_path_arch = name + endif +endforeach + +if gnu_efi_path_arch != '' and EFI_MACHINE_TYPE_NAME == '' + error('gnu-efi is available, but EFI_MACHINE_TYPE_NAME is unknown') +endif + +efi_libdir = get_option('efi-libdir') +if efi_libdir == '' + cmd = 'cd /usr/lib/$(@0@ -print-multi-os-directory) && pwd'.format(efi_cc) + ret = run_command('sh', '-c', cmd) + if ret.returncode() == 0 + efi_libdir = ret.stdout().strip() + endif +endif + +have_gnu_efi = gnu_efi_path_arch != '' and efi_libdir != '' + +if not have_gnu_efi + error('gnu-efi support requested, but headers were not found') +endif + +arch_lds = 'elf_@0@_efi.lds'.format(gnu_efi_path_arch) +if efi_ldsdir == '' + efi_ldsdir = join_paths(efi_libdir, 'gnuefi') + cmd = run_command('test', '-f', join_paths(efi_ldsdir, arch_lds)) + if cmd.returncode() != 0 + efi_ldsdir = efi_libdir + cmd = run_command('test', '-f', join_paths(efi_ldsdir, arch_lds)) + if cmd.returncode() != 0 + error('Cannot find @0@'.format(arch_lds)) + endif + endif +endif + +message('efi-libdir: "@0@"'.format(efi_libdir)) +message('efi-ldsdir: "@0@"'.format(efi_ldsdir)) +message('efi-includedir: "@0@"'.format(efi_incdir)) + +debugdir = join_paths (libdir, 'debug') +compile_args = ['-Og', + '-g3', + '-Wp,-D_FORTIFY_SOURCE=2', + '--param=ssp-buffer-size=4', + '-fexceptions', + '-Wall', + '-Werror', + '-Wextra', + '-std=gnu11', + '-fpic', + '-fshort-wchar', + '-ffreestanding', + '-fno-strict-aliasing', + '-fno-stack-protector', + '-fno-stack-check', + '-fno-merge-constants', + '-Wsign-compare', + '-Wno-missing-field-initializers', + '-grecord-gcc-switches', + '-DDEBUGDIR="@0@"'.format(debugdir), + '-isystem', efi_incdir, + '-isystem', join_paths(efi_incdir, gnu_efi_path_arch)] +if efi_arch == 'x86_64' + compile_args += ['-mno-red-zone', + '-mno-sse', + '-mno-mmx', + '-DEFI_FUNCTION_WRAPPER', + '-DGNU_EFI_USE_MS_ABI'] +elif efi_arch == 'ia32' + compile_args += ['-mno-sse', + '-mno-mmx', + '-mno-red-zone', + '-m32'] +# no special cases for aarch64 or arm +endif + +efi_ldflags = ['-T', + join_paths(efi_ldsdir, arch_lds), + '-shared', + '-Bsymbolic', + '-nostdlib', + '-znocombreloc', + '-L', efi_libdir, + join_paths(efi_ldsdir, 'crt0-efi-@0@.o'.format(gnu_efi_path_arch))] +if efi_arch == 'aarch64' or efi_arch == 'arm' + # Aarch64 and ARM32 don't have an EFI capable objcopy. Use 'binary' + # instead, and add required symbols manually. + efi_ldflags += ['--defsym=EFI_SUBSYSTEM=0xa'] + efi_format = ['-O', 'binary'] +else + efi_format = ['--target=efi-app-@0@'.format(gnu_efi_arch)] +endif + +libgcc_file_name = run_command(efi_cc, '-print-libgcc-file-name').stdout().strip() +efi_name = 'fwup@0@.efi'.format(EFI_MACHINE_TYPE_NAME) + +o_file = custom_target('fwupdate.o', + input : 'fwupdate.c', + output : 'fwupdate.o', + command : [efi_cc, '-c', '@INPUT@', '-o', '@OUTPUT@'] + + compile_args) + +so = custom_target('fwup.so', + input : o_file, + output : 'fwup.so', + command : [efi_ld, '-o', '@OUTPUT@'] + + efi_ldflags + ['@INPUT@'] + + ['-lefi', '-lgnuefi', libgcc_file_name]) + +app = custom_target(efi_name, + input : so, + output : efi_name, + command : [objcopy, + '-j', '.text', + '-j', '.sdata', + '-j', '.data', + '-j', '.dynamic', + '-j', '.dynsym', + '-j', '.rel', + '-j', '.rela', + '-j', '.reloc'] + + efi_format + + ['@INPUT@', '@OUTPUT@'], + install : true, + install_dir : efi_app_location) + +dbg = custom_target('efi_debug', + input : so, + output : efi_name + '.debug', + command : [objcopy, + '-j', '.text', + '-j', '.sdata', + '-j', '.data', + '-j', '.dynamic', + '-j', '.dynsym', + '-j', '.rel*', + '-j', '.rela*', + '-j', '.reloc', + '-j', '.eh_frame', + '-j', '.debug*', + '-j', '.note.gnu.build-id'] + + efi_format + + ['@INPUT@', '@OUTPUT@'], + install : false, + install_dir : debugdir) diff --git a/plugins/uefi/meson.build b/plugins/uefi/meson.build index a3940f6f5..36f1ce517 100644 --- a/plugins/uefi/meson.build +++ b/plugins/uefi/meson.build @@ -63,3 +63,4 @@ if get_option('tests') test('uefi-self-test', e) endif +subdir('efi')