Skip to content

Commit b5ac1d4

Browse files
medhefgobluca
authored andcommitted
boot: Read files in small chunks on broken firmware
Fixes: #25911 (cherry picked from commit f70f992) (cherry picked from commit 1a0f2c5)
1 parent 3c7f7cb commit b5ac1d4

File tree

3 files changed

+44
-7
lines changed

3 files changed

+44
-7
lines changed

src/boot/efi/boot.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2327,7 +2327,7 @@ static EFI_STATUS initrd_prepare(
23272327
return EFI_OUT_OF_RESOURCES;
23282328
initrd = xrealloc(initrd, size, new_size);
23292329

2330-
err = handle->Read(handle, &read_size, initrd + size);
2330+
err = chunked_read(handle, &read_size, initrd + size);
23312331
if (err != EFI_SUCCESS)
23322332
return err;
23332333

src/boot/efi/util.c

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,44 @@ void mangle_stub_cmdline(char16_t *cmdline) {
288288
}
289289
}
290290

291+
EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf) {
292+
EFI_STATUS err;
293+
294+
assert(file);
295+
assert(size);
296+
assert(buf);
297+
298+
/* This is a drop-in replacement for EFI_FILE->Read() with the same API behavior.
299+
* Some broken firmwares cannot handle large file reads and will instead return
300+
* an error. As a workaround, read such files in small chunks.
301+
* Note that we cannot just try reading the whole file first on such firmware as
302+
* that will permanently break the handle even if it is re-opened.
303+
*
304+
* https://github.com/systemd/systemd/issues/25911 */
305+
306+
if (*size == 0)
307+
return EFI_SUCCESS;
308+
309+
size_t read = 0, remaining = *size;
310+
while (remaining > 0) {
311+
size_t chunk = MIN(1024U * 1024U, remaining);
312+
313+
err = file->Read(file, &chunk, (uint8_t *) buf + read);
314+
if (err != EFI_SUCCESS)
315+
return err;
316+
if (chunk == 0)
317+
/* Caller requested more bytes than are in file. */
318+
break;
319+
320+
assert(chunk <= remaining);
321+
read += chunk;
322+
remaining -= chunk;
323+
}
324+
325+
*size = read;
326+
return EFI_SUCCESS;
327+
}
328+
291329
EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, UINTN off, UINTN size, char **ret, UINTN *ret_size) {
292330
_cleanup_(file_closep) EFI_FILE *handle = NULL;
293331
_cleanup_free_ char *buf = NULL;
@@ -321,13 +359,11 @@ EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, UINTN off, UINTN size,
321359
UINTN extra = size % sizeof(char16_t) + sizeof(char16_t);
322360

323361
buf = xmalloc(size + extra);
324-
if (size > 0) {
325-
err = handle->Read(handle, &size, buf);
326-
if (err != EFI_SUCCESS)
327-
return err;
328-
}
362+
err = chunked_read(handle, &size, buf);
363+
if (err != EFI_SUCCESS)
364+
return err;
329365

330-
/* Note that handle->Read() changes size to reflect the actually bytes read. */
366+
/* Note that chunked_read() changes size to reflect the actual bytes read. */
331367
memset(buf + size, 0, extra);
332368

333369
*ret = TAKE_PTR(buf);

src/boot/efi/util.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ char16_t *xstr8_to_path(const char *stra);
117117
void mangle_stub_cmdline(char16_t *cmdline);
118118

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

121122
static inline void file_closep(EFI_FILE **handle) {
122123
if (!*handle)

0 commit comments

Comments
 (0)