Skip to content

Commit

Permalink
boot: Read files in small chunks on broken firmware
Browse files Browse the repository at this point in the history
Fixes: systemd#25911
(cherry picked from commit f70f992)
(cherry picked from commit 1a0f2c5)
  • Loading branch information
medhefgo authored and bluca committed Jul 18, 2023
1 parent 3c7f7cb commit b5ac1d4
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 7 deletions.
2 changes: 1 addition & 1 deletion src/boot/efi/boot.c
Original file line number Diff line number Diff line change
Expand Up @@ -2327,7 +2327,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;

Expand Down
48 changes: 42 additions & 6 deletions src/boot/efi/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,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, UINTN off, UINTN size, char **ret, UINTN *ret_size) {
_cleanup_(file_closep) EFI_FILE *handle = NULL;
_cleanup_free_ char *buf = NULL;
Expand Down Expand Up @@ -321,13 +359,11 @@ EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, UINTN off, UINTN size,
UINTN 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);
Expand Down
1 change: 1 addition & 0 deletions src/boot/efi/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ char16_t *xstr8_to_path(const char *stra);
void mangle_stub_cmdline(char16_t *cmdline);

EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, UINTN off, UINTN size, char **content, UINTN *content_size);
EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf);

static inline void file_closep(EFI_FILE **handle) {
if (!*handle)
Expand Down

0 comments on commit b5ac1d4

Please sign in to comment.