Re: [PATCH v4 01/13] efi/cper: Reject CPER records with an out-of-range error_data_length

Jonathan Cameron <[email protected]>
Newsgroups org.kernel.vger.linux-acpi,org.kernel.vger.linux-cxl
Message-ID <20260824225720.60a4fdcf@jic23-huawei>
On Mon, 24 Aug 2026 10:49:24 -0700
Dave Jiang <[email protected]> wrote:

> cper_estatus_check() sizes each section with acpi_hest_get_record_size(),
> which adds the firmware-controlled u32 error_data_length to the header size
> as a signed int (see <acpi/ghes.h>). A value in the top sizeof(*gdata)
> bytes of the u32 range wraps the sum small rather than large, so it slips
> past the "record_size > data_len" check: against the 72-byte v300 header,
> 0xffffffb9 sizes the record at 1 and acpi_hest_get_next() walks it a byte
> at a time, off the end. 0xffffffb8 sizes it at 0 and loops forever.
> 
> Sum in u64 so the check sees the real size, and reject a size the int
> helpers cannot carry, since the walk advances by their return value.
> 
> This is the per-section upper bound the later "len < sizeof(*foo)" guards
> rely on; they are lower bounds only.
> 
> Reported-by: [email protected]
> Closes: https://sashiko.dev/#/patchset/[email protected]?part=1
> Fixes: 45b14a4ffcc1 ("efi: cper: Fix possible out-of-bounds access")
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Dave Jiang <[email protected]>
I know I'm late to the discussion but maybe it would just be simpler to
use check_add_overflow()?

> ---
> v4:
> - Do the arithmetic in u64 at the choke point instead of bounding the u32
>   error_data_length against data_len, so the check no longer depends on the
>   signed helpers in <acpi/ghes.h> behaving (Tony Luck). Bounding the u32
>   still left a window when data_length itself was within a header of
>   U32_MAX, and it did not stop acpi_hest_get_next() advancing by a wrapped
>   int. The v3 "< 0" arm was dead either way, since data_len is unsigned and
>   promoted the int back (Tony Luck, Shuai Xue).
> - Dropped Alison's and Shuai's Reviewed-by; the check was reworked after
>   they reviewed it.
> ---
>  drivers/firmware/efi/cper.c | 16 +++++++++++++---
>  1 file changed, 13 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c
> index 06b4fdb59917..ec092729cacc 100644
> --- a/drivers/firmware/efi/cper.c
> +++ b/drivers/firmware/efi/cper.c
> @@ -752,7 +752,7 @@ EXPORT_SYMBOL_GPL(cper_estatus_check_header);
>  int cper_estatus_check(const struct acpi_hest_generic_status *estatus)
>  {
>  	struct acpi_hest_generic_data *gdata;
> -	unsigned int data_len, record_size;
> +	unsigned int data_len;
>  	int rc;
>  
>  	rc = cper_estatus_check_header(estatus);
> @@ -762,11 +762,21 @@ int cper_estatus_check(const struct acpi_hest_generic_status *estatus)
>  	data_len = estatus->data_length;
>  
>  	apei_estatus_for_each_section(estatus, gdata) {
> +		u64 record_size;
> +
>  		if (acpi_hest_get_size(gdata) > data_len)
>  			return -EINVAL;
>  
> -		record_size = acpi_hest_get_record_size(gdata);
> -		if (record_size > data_len)
> +		/*
> +		 * acpi_hest_get_record_size() sums these as a signed int (see
> +		 * <acpi/ghes.h>), which wraps small for a huge
> +		 * error_data_length and slips past the check below. Sum in u64,
> +		 * and reject what those helpers cannot carry, since the walk
> +		 * advances by their return value.
> +		 */
> +		record_size = (u64)acpi_hest_get_size(gdata) +
> +			      gdata->error_data_length;

I'm late to the game obviously and what you have works but could this have
used some explicit overflow checking?  Something like

		if (check_add_overflow(acpi_hest_get_size(gdata),
				       gdata->error_data_length, &record_size))
			return -EINVAL;

		if (record_size > data_len)
			return -EINVAL;

That uses the compiler __builtin_add_overflow() which checks if the infinite
precision result of the sum of the parameters would have wrapped when written
to the output one.

I think you could then drop the earlier check as well as
record_size is at least as big as acpi_hest_get_size() and we know there
was no wrap around.

	
> +		if (record_size > data_len || record_size > INT_MAX)
>  			return -EINVAL;
>  
>  		data_len -= record_size;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.