Commit 2163aff6 authored by Richard Fitzgerald's avatar Richard Fitzgerald Committed by Mark Brown

firmware: cs_dsp: Prevent buffer overrun when processing V2 alg headers

Check that all fields of a V2 algorithm header fit into the available
firmware data buffer.

The wmfw V2 format introduced variable-length strings in the algorithm
block header. This means the overall header length is variable, and the
position of most fields varies depending on the length of the string
fields. Each field must be checked to ensure that it does not overflow
the firmware data buffer.

As this ia bugfix patch, the fixes avoid making any significant change to
the existing code. This makes it easier to review and less likely to
introduce new bugs.
Signed-off-by: default avatarRichard Fitzgerald <rf@opensource.cirrus.com>
Fixes: f6bc909e ("firmware: cs_dsp: add driver to support firmware loading on Cirrus Logic DSPs")
Link: https://patch.msgid.link/20240627141432.93056-5-rf@opensource.cirrus.comSigned-off-by: default avatarMark Brown <broonie@kernel.org>
parent 6598afa9
...@@ -1107,9 +1107,16 @@ struct cs_dsp_coeff_parsed_coeff { ...@@ -1107,9 +1107,16 @@ struct cs_dsp_coeff_parsed_coeff {
int len; int len;
}; };
static int cs_dsp_coeff_parse_string(int bytes, const u8 **pos, const u8 **str) static int cs_dsp_coeff_parse_string(int bytes, const u8 **pos, unsigned int avail,
const u8 **str)
{ {
int length; int length, total_field_len;
/* String fields are at least one __le32 */
if (sizeof(__le32) > avail) {
*pos = NULL;
return 0;
}
switch (bytes) { switch (bytes) {
case 1: case 1:
...@@ -1122,10 +1129,16 @@ static int cs_dsp_coeff_parse_string(int bytes, const u8 **pos, const u8 **str) ...@@ -1122,10 +1129,16 @@ static int cs_dsp_coeff_parse_string(int bytes, const u8 **pos, const u8 **str)
return 0; return 0;
} }
total_field_len = ((length + bytes) + 3) & ~0x03;
if ((unsigned int)total_field_len > avail) {
*pos = NULL;
return 0;
}
if (str) if (str)
*str = *pos + bytes; *str = *pos + bytes;
*pos += ((length + bytes) + 3) & ~0x03; *pos += total_field_len;
return length; return length;
} }
...@@ -1150,51 +1163,100 @@ static int cs_dsp_coeff_parse_int(int bytes, const u8 **pos) ...@@ -1150,51 +1163,100 @@ static int cs_dsp_coeff_parse_int(int bytes, const u8 **pos)
return val; return val;
} }
static inline void cs_dsp_coeff_parse_alg(struct cs_dsp *dsp, const u8 **data, static int cs_dsp_coeff_parse_alg(struct cs_dsp *dsp,
struct cs_dsp_coeff_parsed_alg *blk) const struct wmfw_region *region,
struct cs_dsp_coeff_parsed_alg *blk)
{ {
const struct wmfw_adsp_alg_data *raw; const struct wmfw_adsp_alg_data *raw;
unsigned int data_len = le32_to_cpu(region->len);
unsigned int pos;
const u8 *tmp;
raw = (const struct wmfw_adsp_alg_data *)region->data;
switch (dsp->fw_ver) { switch (dsp->fw_ver) {
case 0: case 0:
case 1: case 1:
raw = (const struct wmfw_adsp_alg_data *)*data; if (sizeof(*raw) > data_len)
*data = raw->data; return -EOVERFLOW;
blk->id = le32_to_cpu(raw->id); blk->id = le32_to_cpu(raw->id);
blk->name = raw->name; blk->name = raw->name;
blk->name_len = strlen(raw->name); blk->name_len = strlen(raw->name);
blk->ncoeff = le32_to_cpu(raw->ncoeff); blk->ncoeff = le32_to_cpu(raw->ncoeff);
pos = sizeof(*raw);
break; break;
default: default:
blk->id = cs_dsp_coeff_parse_int(sizeof(raw->id), data); if (sizeof(raw->id) > data_len)
blk->name_len = cs_dsp_coeff_parse_string(sizeof(u8), data, return -EOVERFLOW;
tmp = region->data;
blk->id = cs_dsp_coeff_parse_int(sizeof(raw->id), &tmp);
pos = tmp - region->data;
tmp = &region->data[pos];
blk->name_len = cs_dsp_coeff_parse_string(sizeof(u8), &tmp, data_len - pos,
&blk->name); &blk->name);
cs_dsp_coeff_parse_string(sizeof(u16), data, NULL); if (!tmp)
blk->ncoeff = cs_dsp_coeff_parse_int(sizeof(raw->ncoeff), data); return -EOVERFLOW;
pos = tmp - region->data;
cs_dsp_coeff_parse_string(sizeof(u16), &tmp, data_len - pos, NULL);
if (!tmp)
return -EOVERFLOW;
pos = tmp - region->data;
if (sizeof(raw->ncoeff) > (data_len - pos))
return -EOVERFLOW;
blk->ncoeff = cs_dsp_coeff_parse_int(sizeof(raw->ncoeff), &tmp);
pos += sizeof(raw->ncoeff);
break; break;
} }
if ((int)blk->ncoeff < 0)
return -EOVERFLOW;
cs_dsp_dbg(dsp, "Algorithm ID: %#x\n", blk->id); cs_dsp_dbg(dsp, "Algorithm ID: %#x\n", blk->id);
cs_dsp_dbg(dsp, "Algorithm name: %.*s\n", blk->name_len, blk->name); cs_dsp_dbg(dsp, "Algorithm name: %.*s\n", blk->name_len, blk->name);
cs_dsp_dbg(dsp, "# of coefficient descriptors: %#x\n", blk->ncoeff); cs_dsp_dbg(dsp, "# of coefficient descriptors: %#x\n", blk->ncoeff);
return pos;
} }
static inline void cs_dsp_coeff_parse_coeff(struct cs_dsp *dsp, const u8 **data, static int cs_dsp_coeff_parse_coeff(struct cs_dsp *dsp,
struct cs_dsp_coeff_parsed_coeff *blk) const struct wmfw_region *region,
unsigned int pos,
struct cs_dsp_coeff_parsed_coeff *blk)
{ {
const struct wmfw_adsp_coeff_data *raw; const struct wmfw_adsp_coeff_data *raw;
unsigned int data_len = le32_to_cpu(region->len);
unsigned int blk_len, blk_end_pos;
const u8 *tmp; const u8 *tmp;
int length;
raw = (const struct wmfw_adsp_coeff_data *)&region->data[pos];
if (sizeof(raw->hdr) > (data_len - pos))
return -EOVERFLOW;
blk_len = le32_to_cpu(raw->hdr.size);
if (blk_len > S32_MAX)
return -EOVERFLOW;
if (blk_len > (data_len - pos - sizeof(raw->hdr)))
return -EOVERFLOW;
blk_end_pos = pos + sizeof(raw->hdr) + blk_len;
blk->offset = le16_to_cpu(raw->hdr.offset);
blk->mem_type = le16_to_cpu(raw->hdr.type);
switch (dsp->fw_ver) { switch (dsp->fw_ver) {
case 0: case 0:
case 1: case 1:
raw = (const struct wmfw_adsp_coeff_data *)*data; if (sizeof(*raw) > (data_len - pos))
*data = *data + sizeof(raw->hdr) + le32_to_cpu(raw->hdr.size); return -EOVERFLOW;
blk->offset = le16_to_cpu(raw->hdr.offset);
blk->mem_type = le16_to_cpu(raw->hdr.type);
blk->name = raw->name; blk->name = raw->name;
blk->name_len = strlen(raw->name); blk->name_len = strlen(raw->name);
blk->ctl_type = le16_to_cpu(raw->ctl_type); blk->ctl_type = le16_to_cpu(raw->ctl_type);
...@@ -1202,19 +1264,33 @@ static inline void cs_dsp_coeff_parse_coeff(struct cs_dsp *dsp, const u8 **data, ...@@ -1202,19 +1264,33 @@ static inline void cs_dsp_coeff_parse_coeff(struct cs_dsp *dsp, const u8 **data,
blk->len = le32_to_cpu(raw->len); blk->len = le32_to_cpu(raw->len);
break; break;
default: default:
tmp = *data; pos += sizeof(raw->hdr);
blk->offset = cs_dsp_coeff_parse_int(sizeof(raw->hdr.offset), &tmp); tmp = &region->data[pos];
blk->mem_type = cs_dsp_coeff_parse_int(sizeof(raw->hdr.type), &tmp); blk->name_len = cs_dsp_coeff_parse_string(sizeof(u8), &tmp, data_len - pos,
length = cs_dsp_coeff_parse_int(sizeof(raw->hdr.size), &tmp);
blk->name_len = cs_dsp_coeff_parse_string(sizeof(u8), &tmp,
&blk->name); &blk->name);
cs_dsp_coeff_parse_string(sizeof(u8), &tmp, NULL); if (!tmp)
cs_dsp_coeff_parse_string(sizeof(u16), &tmp, NULL); return -EOVERFLOW;
pos = tmp - region->data;
cs_dsp_coeff_parse_string(sizeof(u8), &tmp, data_len - pos, NULL);
if (!tmp)
return -EOVERFLOW;
pos = tmp - region->data;
cs_dsp_coeff_parse_string(sizeof(u16), &tmp, data_len - pos, NULL);
if (!tmp)
return -EOVERFLOW;
pos = tmp - region->data;
if (sizeof(raw->ctl_type) + sizeof(raw->flags) + sizeof(raw->len) >
(data_len - pos))
return -EOVERFLOW;
blk->ctl_type = cs_dsp_coeff_parse_int(sizeof(raw->ctl_type), &tmp); blk->ctl_type = cs_dsp_coeff_parse_int(sizeof(raw->ctl_type), &tmp);
pos += sizeof(raw->ctl_type);
blk->flags = cs_dsp_coeff_parse_int(sizeof(raw->flags), &tmp); blk->flags = cs_dsp_coeff_parse_int(sizeof(raw->flags), &tmp);
pos += sizeof(raw->flags);
blk->len = cs_dsp_coeff_parse_int(sizeof(raw->len), &tmp); blk->len = cs_dsp_coeff_parse_int(sizeof(raw->len), &tmp);
*data = *data + sizeof(raw->hdr) + length;
break; break;
} }
...@@ -1224,6 +1300,8 @@ static inline void cs_dsp_coeff_parse_coeff(struct cs_dsp *dsp, const u8 **data, ...@@ -1224,6 +1300,8 @@ static inline void cs_dsp_coeff_parse_coeff(struct cs_dsp *dsp, const u8 **data,
cs_dsp_dbg(dsp, "\tCoefficient flags: %#x\n", blk->flags); cs_dsp_dbg(dsp, "\tCoefficient flags: %#x\n", blk->flags);
cs_dsp_dbg(dsp, "\tALSA control type: %#x\n", blk->ctl_type); cs_dsp_dbg(dsp, "\tALSA control type: %#x\n", blk->ctl_type);
cs_dsp_dbg(dsp, "\tALSA control len: %#x\n", blk->len); cs_dsp_dbg(dsp, "\tALSA control len: %#x\n", blk->len);
return blk_end_pos;
} }
static int cs_dsp_check_coeff_flags(struct cs_dsp *dsp, static int cs_dsp_check_coeff_flags(struct cs_dsp *dsp,
...@@ -1247,12 +1325,16 @@ static int cs_dsp_parse_coeff(struct cs_dsp *dsp, ...@@ -1247,12 +1325,16 @@ static int cs_dsp_parse_coeff(struct cs_dsp *dsp,
struct cs_dsp_alg_region alg_region = {}; struct cs_dsp_alg_region alg_region = {};
struct cs_dsp_coeff_parsed_alg alg_blk; struct cs_dsp_coeff_parsed_alg alg_blk;
struct cs_dsp_coeff_parsed_coeff coeff_blk; struct cs_dsp_coeff_parsed_coeff coeff_blk;
const u8 *data = region->data; int i, pos, ret;
int i, ret;
pos = cs_dsp_coeff_parse_alg(dsp, region, &alg_blk);
if (pos < 0)
return pos;
cs_dsp_coeff_parse_alg(dsp, &data, &alg_blk);
for (i = 0; i < alg_blk.ncoeff; i++) { for (i = 0; i < alg_blk.ncoeff; i++) {
cs_dsp_coeff_parse_coeff(dsp, &data, &coeff_blk); pos = cs_dsp_coeff_parse_coeff(dsp, region, pos, &coeff_blk);
if (pos < 0)
return pos;
switch (coeff_blk.ctl_type) { switch (coeff_blk.ctl_type) {
case WMFW_CTL_TYPE_BYTES: case WMFW_CTL_TYPE_BYTES:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment