From: Jan Janssen Date: Thu, 5 Jan 2023 17:35:19 +0000 (+0100) Subject: boot: Read files in small chunks on broken firmware X-Git-Tag: v254-rc1~424 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f70f992273a7add1ec98a894ffadb1f1e43c0c31;p=thirdparty%2Fsystemd.git boot: Read files in small chunks on broken firmware Fixes: #25911 --- diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 6959482ab6a..65294f3c090 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -2306,7 +2306,7 @@ static EFI_STATUS initrd_prepare( return EFI_OUT_OF_RESOURCES; initrd = xrealloc(initrd, size, new_size); - err = handle->Read(handle, &read_size, initrd + size); + err = chunked_read(handle, &read_size, initrd + size); if (err != EFI_SUCCESS) return err; diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c index c8bbe752779..b339021fe97 100644 --- a/src/boot/efi/util.c +++ b/src/boot/efi/util.c @@ -282,6 +282,44 @@ void mangle_stub_cmdline(char16_t *cmdline) { } } +EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf) { + EFI_STATUS err; + + assert(file); + assert(size); + assert(buf); + + /* This is a drop-in replacement for EFI_FILE->Read() with the same API behavior. + * Some broken firmwares cannot handle large file reads and will instead return + * an error. As a workaround, read such files in small chunks. + * Note that we cannot just try reading the whole file first on such firmware as + * that will permanently break the handle even if it is re-opened. + * + * https://github.com/systemd/systemd/issues/25911 */ + + if (*size == 0) + return EFI_SUCCESS; + + size_t read = 0, remaining = *size; + while (remaining > 0) { + size_t chunk = MIN(1024U * 1024U, remaining); + + err = file->Read(file, &chunk, (uint8_t *) buf + read); + if (err != EFI_SUCCESS) + return err; + if (chunk == 0) + /* Caller requested more bytes than are in file. */ + break; + + assert(chunk <= remaining); + read += chunk; + remaining -= chunk; + } + + *size = read; + return EFI_SUCCESS; +} + EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, size_t off, size_t size, char **ret, size_t *ret_size) { _cleanup_(file_closep) EFI_FILE *handle = NULL; _cleanup_free_ char *buf = NULL; @@ -315,13 +353,11 @@ EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, size_t off, size_t siz size_t extra = size % sizeof(char16_t) + sizeof(char16_t); buf = xmalloc(size + extra); - if (size > 0) { - err = handle->Read(handle, &size, buf); - if (err != EFI_SUCCESS) - return err; - } + err = chunked_read(handle, &size, buf); + if (err != EFI_SUCCESS) + return err; - /* Note that handle->Read() changes size to reflect the actually bytes read. */ + /* Note that chunked_read() changes size to reflect the actual bytes read. */ memset(buf + size, 0, extra); *ret = TAKE_PTR(buf); diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index 9058918a939..bb917faf554 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -97,6 +97,7 @@ void convert_efi_path(char16_t *path); char16_t *xstr8_to_path(const char *stra); void mangle_stub_cmdline(char16_t *cmdline); +EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf); EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, size_t off, size_t size, char **content, size_t *content_size); static inline void file_closep(EFI_FILE **handle) {