@@ -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+
291329EFI_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 );
0 commit comments