kern/disk: Limit recursion depth

The grub_disk_read() may trigger other disk reads, e.g. via loopbacks.
This may lead to very deep recursion which can corrupt the heap. So, fix
the issue by limiting reads depth.

Reported-by: B Horn <b@horn.uk>
Signed-off-by: B Horn <b@horn.uk>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
This commit is contained in:
B Horn 2024-05-12 04:09:24 +01:00 committed by Daniel Kiper
parent 67f70f70a3
commit 18212f0648
2 changed files with 22 additions and 8 deletions

View File

@ -28,6 +28,10 @@
#define GRUB_CACHE_TIMEOUT 2 #define GRUB_CACHE_TIMEOUT 2
/* Disk reads may trigger other disk reads. So, limit recursion depth. */
#define MAX_READ_RECURSION_DEPTH 16
static unsigned int read_recursion_depth = 0;
/* The last time the disk was used. */ /* The last time the disk was used. */
static grub_uint64_t grub_last_time = 0; static grub_uint64_t grub_last_time = 0;
@ -417,6 +421,8 @@ grub_err_t
grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector, grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector,
grub_off_t offset, grub_size_t size, void *buf) grub_off_t offset, grub_size_t size, void *buf)
{ {
grub_err_t err = GRUB_ERR_NONE;
/* First of all, check if the region is within the disk. */ /* First of all, check if the region is within the disk. */
if (grub_disk_adjust_range (disk, &sector, &offset, size) != GRUB_ERR_NONE) if (grub_disk_adjust_range (disk, &sector, &offset, size) != GRUB_ERR_NONE)
{ {
@ -427,12 +433,17 @@ grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector,
return grub_errno; return grub_errno;
} }
if (++read_recursion_depth >= MAX_READ_RECURSION_DEPTH)
{
grub_error (GRUB_ERR_RECURSION_DEPTH, "grub_disk_read(): Maximum recursion depth exceeded");
goto error;
}
/* First read until first cache boundary. */ /* First read until first cache boundary. */
if (offset || (sector & (GRUB_DISK_CACHE_SIZE - 1))) if (offset || (sector & (GRUB_DISK_CACHE_SIZE - 1)))
{ {
grub_disk_addr_t start_sector; grub_disk_addr_t start_sector;
grub_size_t pos; grub_size_t pos;
grub_err_t err;
grub_size_t len; grub_size_t len;
start_sector = sector & ~((grub_disk_addr_t) GRUB_DISK_CACHE_SIZE - 1); start_sector = sector & ~((grub_disk_addr_t) GRUB_DISK_CACHE_SIZE - 1);
@ -444,7 +455,7 @@ grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector,
err = grub_disk_read_small (disk, start_sector, err = grub_disk_read_small (disk, start_sector,
offset + pos, len, buf); offset + pos, len, buf);
if (err) if (err)
return err; goto error;
buf = (char *) buf + len; buf = (char *) buf + len;
size -= len; size -= len;
offset += len; offset += len;
@ -457,7 +468,6 @@ grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector,
{ {
char *data = NULL; char *data = NULL;
grub_disk_addr_t agglomerate; grub_disk_addr_t agglomerate;
grub_err_t err;
/* agglomerate read until we find a first cached entry. */ /* agglomerate read until we find a first cached entry. */
for (agglomerate = 0; agglomerate for (agglomerate = 0; agglomerate
@ -493,7 +503,7 @@ grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector,
- disk->log_sector_size), - disk->log_sector_size),
buf); buf);
if (err) if (err)
return err; goto error;
for (i = 0; i < agglomerate; i ++) for (i = 0; i < agglomerate; i ++)
grub_disk_cache_store (disk->dev->id, disk->id, grub_disk_cache_store (disk->dev->id, disk->id,
@ -527,13 +537,16 @@ grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector,
/* And now read the last part. */ /* And now read the last part. */
if (size) if (size)
{ {
grub_err_t err;
err = grub_disk_read_small (disk, sector, 0, size, buf); err = grub_disk_read_small (disk, sector, 0, size, buf);
if (err) if (err)
return err; goto error;
} }
return grub_errno; err = grub_errno;
error:
read_recursion_depth--;
return err;
} }
grub_uint64_t grub_uint64_t

View File

@ -74,7 +74,8 @@ typedef enum
GRUB_ERR_EOF, GRUB_ERR_EOF,
GRUB_ERR_BAD_SIGNATURE, GRUB_ERR_BAD_SIGNATURE,
GRUB_ERR_BAD_FIRMWARE, GRUB_ERR_BAD_FIRMWARE,
GRUB_ERR_STILL_REFERENCED GRUB_ERR_STILL_REFERENCED,
GRUB_ERR_RECURSION_DEPTH
} }
grub_err_t; grub_err_t;