Skip to content

Commit

Permalink
lib/firmware_table: Provide buffer length argument to cdat_table_parse()
Browse files Browse the repository at this point in the history
There exist card implementations with a CDAT table using a fixed size
buffer, but with entries filled in that do not fill the whole table
length size. Then, the last entry in the CDAT table may not mark the
end of the CDAT table buffer specified by the length field in the CDAT
header. It can be shorter with trailing unused (zero'ed) data. The
actual table length is determined while reading all CDAT entries of
the table with DOE.

If the table is greater than expected (containing zero'ed trailing
data), the CDAT parser fails with:

 [   48.691717] Malformed DSMAS table length: (24:0)
 [   48.702084] [CDAT:0x00] Invalid zero length
 [   48.711460] cxl_port endpoint1: Failed to parse CDAT: -22

In addition, a check of the table buffer length is missing to prevent
an out-of-bound access then parsing the CDAT table.

Hardening code against device returning borked table. Fix that by
providing an optional buffer length argument to
acpi_parse_entries_array() that can be used by cdat_table_parse() to
propagate the buffer size down to its users to check the buffer
length. This also prevents a possible out-of-bound access mentioned.

Add a check to warn about a malformed CDAT table length.

Cc: Rafael J. Wysocki <[email protected]>
Cc: Len Brown <[email protected]>
Reviewed-by: Dave Jiang <[email protected]>
Signed-off-by: Robert Richter <[email protected]>
Reviewed-by: Jonathan Cameron <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Dan Williams <[email protected]>
  • Loading branch information
Robert Richter authored and djbw committed Mar 13, 2024
1 parent e0c818e commit c6c3187
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 11 deletions.
2 changes: 1 addition & 1 deletion drivers/acpi/tables.c
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ int __init_or_acpilib acpi_table_parse_entries_array(

count = acpi_parse_entries_array(id, table_size,
(union fw_table_header *)table_header,
proc, proc_num, max_entries);
0, proc, proc_num, max_entries);

acpi_put_table(table_header);
return count;
Expand Down
6 changes: 3 additions & 3 deletions drivers/cxl/core/cdat.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,13 @@ static int cxl_cdat_endpoint_process(struct cxl_port *port,
int rc;

rc = cdat_table_parse(ACPI_CDAT_TYPE_DSMAS, cdat_dsmas_handler,
dsmas_xa, port->cdat.table);
dsmas_xa, port->cdat.table, port->cdat.length);
rc = cdat_table_parse_output(rc);
if (rc)
return rc;

rc = cdat_table_parse(ACPI_CDAT_TYPE_DSLBIS, cdat_dslbis_handler,
dsmas_xa, port->cdat.table);
dsmas_xa, port->cdat.table, port->cdat.length);
return cdat_table_parse_output(rc);
}

Expand Down Expand Up @@ -477,7 +477,7 @@ void cxl_switch_parse_cdat(struct cxl_port *port)
return;

rc = cdat_table_parse(ACPI_CDAT_TYPE_SSLBIS, cdat_sslbis_handler,
port, port->cdat.table);
port, port->cdat.table, port->cdat.length);
rc = cdat_table_parse_output(rc);
if (rc)
dev_dbg(&port->dev, "Failed to parse SSLBIS: %d\n", rc);
Expand Down
8 changes: 7 additions & 1 deletion drivers/cxl/core/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@ void read_cdat_data(struct cxl_port *port)
struct pci_dev *pdev = NULL;
struct cxl_memdev *cxlmd;
struct cdat_doe_rsp *buf;
size_t length;
size_t table_length, length;
int rc;

if (is_cxl_memdev(uport)) {
Expand Down Expand Up @@ -662,10 +662,16 @@ void read_cdat_data(struct cxl_port *port)
if (!buf)
goto err;

table_length = length;

rc = cxl_cdat_read_table(dev, doe_mb, buf, &length);
if (rc)
goto err;

if (table_length != length)
dev_warn(dev, "Malformed CDAT table length (%zu:%zu), discarding trailing data\n",
table_length, length);

if (cdat_checksum(buf->data, length))
goto err;

Expand Down
4 changes: 3 additions & 1 deletion include/linux/fw_table.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ union acpi_subtable_headers {

int acpi_parse_entries_array(char *id, unsigned long table_size,
union fw_table_header *table_header,
unsigned long max_length,
struct acpi_subtable_proc *proc,
int proc_num, unsigned int max_entries);

int cdat_table_parse(enum acpi_cdat_type type,
acpi_tbl_entry_handler_arg handler_arg, void *arg,
struct acpi_table_cdat *table_header);
struct acpi_table_cdat *table_header,
unsigned long length);

/* CXL is the only non-ACPI consumer of the FIRMWARE_TABLE library */
#if IS_ENABLED(CONFIG_ACPI) && !IS_ENABLED(CONFIG_CXL_BUS)
Expand Down
15 changes: 10 additions & 5 deletions lib/fw_table.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ static __init_or_fwtbl_lib int call_handler(struct acpi_subtable_proc *proc,
*
* @id: table id (for debugging purposes)
* @table_size: size of the root table
* @max_length: maximum size of the table (ignore if 0)
* @table_header: where does the table start?
* @proc: array of acpi_subtable_proc struct containing entry id
* and associated handler with it
Expand All @@ -148,18 +149,21 @@ static __init_or_fwtbl_lib int call_handler(struct acpi_subtable_proc *proc,
int __init_or_fwtbl_lib
acpi_parse_entries_array(char *id, unsigned long table_size,
union fw_table_header *table_header,
unsigned long max_length,
struct acpi_subtable_proc *proc,
int proc_num, unsigned int max_entries)
{
unsigned long table_end, subtable_len, entry_len;
unsigned long table_len, table_end, subtable_len, entry_len;
struct acpi_subtable_entry entry;
enum acpi_subtable_type type;
int count = 0;
int i;

type = acpi_get_subtable_type(id);
table_end = (unsigned long)table_header +
acpi_table_get_length(type, table_header);
table_len = acpi_table_get_length(type, table_header);
if (max_length && max_length < table_len)
table_len = max_length;
table_end = (unsigned long)table_header + table_len;

/* Parse all entries looking for a match. */

Expand Down Expand Up @@ -208,7 +212,8 @@ int __init_or_fwtbl_lib
cdat_table_parse(enum acpi_cdat_type type,
acpi_tbl_entry_handler_arg handler_arg,
void *arg,
struct acpi_table_cdat *table_header)
struct acpi_table_cdat *table_header,
unsigned long length)
{
struct acpi_subtable_proc proc = {
.id = type,
Expand All @@ -222,6 +227,6 @@ cdat_table_parse(enum acpi_cdat_type type,
return acpi_parse_entries_array(ACPI_SIG_CDAT,
sizeof(struct acpi_table_cdat),
(union fw_table_header *)table_header,
&proc, 1, 0);
length, &proc, 1, 0);
}
EXPORT_SYMBOL_FWTBL_LIB(cdat_table_parse);

0 comments on commit c6c3187

Please sign in to comment.