]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
boot: Read files in small chunks on broken firmware
authorJan Janssen <medhefgo@web.de>
Thu, 5 Jan 2023 17:35:19 +0000 (18:35 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Mon, 22 May 2023 09:52:17 +0000 (10:52 +0100)
Fixes: #25911
src/boot/efi/boot.c
src/boot/efi/util.c
src/boot/efi/util.h

index 6959482ab6aea3febfb19b8c1250280c4d4e2d5a..65294f3c090225993361c4c09376802fc42c0923 100644 (file)
@@ -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;
 
index c8bbe7527795f730abf0ffd1bae89a8cc45d4b4a..b339021fe9756fe07ee0da49782cfc980ad1bdb4 100644 (file)
@@ -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);
index 9058918a93915c1d8ed4a05d3902e114cc7f4738..bb917faf5541c685973780064b1e95f908948e55 100644 (file)
@@ -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) {