firmware: Sigma: Prevent out of bounds memory access
The SigmaDSP firmware loader currently does not perform enough boundary size checks when processing the firmware. As a result it is possible that a malformed firmware can cause an out of bounds memory access. This patch adds checks which ensure that both the action header and the payload are completely inside the firmware data boundaries before processing them. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Acked-by: Mike Frysinger <vapier@gentoo.org> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Cc: stable@kernel.org
This commit is contained in:
parent
fc8e6e8668
commit
4f718a29fe
2 changed files with 56 additions and 27 deletions
|
@ -14,13 +14,34 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/sigma.h>
|
||||
|
||||
/* Return: 0==OK, <0==error, =1 ==no more actions */
|
||||
static int
|
||||
process_sigma_action(struct i2c_client *client, struct sigma_firmware *ssfw)
|
||||
static size_t sigma_action_size(struct sigma_action *sa)
|
||||
{
|
||||
size_t payload = 0;
|
||||
|
||||
switch (sa->instr) {
|
||||
case SIGMA_ACTION_WRITEXBYTES:
|
||||
case SIGMA_ACTION_WRITESINGLE:
|
||||
case SIGMA_ACTION_WRITESAFELOAD:
|
||||
payload = sigma_action_len(sa);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
payload = ALIGN(payload, 2);
|
||||
|
||||
return payload + sizeof(struct sigma_action);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a negative error value in case of an error, 0 if processing of
|
||||
* the firmware should be stopped after this action, 1 otherwise.
|
||||
*/
|
||||
static int
|
||||
process_sigma_action(struct i2c_client *client, struct sigma_action *sa)
|
||||
{
|
||||
struct sigma_action *sa = (void *)(ssfw->fw->data + ssfw->pos);
|
||||
size_t len = sigma_action_len(sa);
|
||||
int ret = 0;
|
||||
int ret;
|
||||
|
||||
pr_debug("%s: instr:%i addr:%#x len:%zu\n", __func__,
|
||||
sa->instr, sa->addr, len);
|
||||
|
@ -29,44 +50,50 @@ process_sigma_action(struct i2c_client *client, struct sigma_firmware *ssfw)
|
|||
case SIGMA_ACTION_WRITEXBYTES:
|
||||
case SIGMA_ACTION_WRITESINGLE:
|
||||
case SIGMA_ACTION_WRITESAFELOAD:
|
||||
if (ssfw->fw->size < ssfw->pos + len)
|
||||
return -EINVAL;
|
||||
ret = i2c_master_send(client, (void *)&sa->addr, len);
|
||||
if (ret < 0)
|
||||
return -EINVAL;
|
||||
break;
|
||||
|
||||
case SIGMA_ACTION_DELAY:
|
||||
ret = 0;
|
||||
udelay(len);
|
||||
len = 0;
|
||||
break;
|
||||
|
||||
case SIGMA_ACTION_END:
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* when arrive here ret=0 or sent data */
|
||||
ssfw->pos += sigma_action_size(sa, len);
|
||||
return ssfw->pos == ssfw->fw->size;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
process_sigma_actions(struct i2c_client *client, struct sigma_firmware *ssfw)
|
||||
{
|
||||
pr_debug("%s: processing %p\n", __func__, ssfw);
|
||||
struct sigma_action *sa;
|
||||
size_t size;
|
||||
int ret;
|
||||
|
||||
while (ssfw->pos + sizeof(*sa) <= ssfw->fw->size) {
|
||||
sa = (struct sigma_action *)(ssfw->fw->data + ssfw->pos);
|
||||
|
||||
size = sigma_action_size(sa);
|
||||
ssfw->pos += size;
|
||||
if (ssfw->pos > ssfw->fw->size || size == 0)
|
||||
break;
|
||||
|
||||
ret = process_sigma_action(client, sa);
|
||||
|
||||
while (1) {
|
||||
int ret = process_sigma_action(client, ssfw);
|
||||
pr_debug("%s: action returned %i\n", __func__, ret);
|
||||
if (ret == 1)
|
||||
return 0;
|
||||
else if (ret)
|
||||
|
||||
if (ret <= 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ssfw->pos != ssfw->fw->size)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int process_sigma_firmware(struct i2c_client *client, const char *name)
|
||||
|
@ -89,7 +116,14 @@ int process_sigma_firmware(struct i2c_client *client, const char *name)
|
|||
|
||||
/* then verify the header */
|
||||
ret = -EINVAL;
|
||||
if (fw->size < sizeof(*ssfw_head))
|
||||
|
||||
/*
|
||||
* Reject too small or unreasonable large files. The upper limit has been
|
||||
* chosen a bit arbitrarily, but it should be enough for all practical
|
||||
* purposes and having the limit makes it easier to avoid integer
|
||||
* overflows later in the loading process.
|
||||
*/
|
||||
if (fw->size < sizeof(*ssfw_head) || fw->size >= 0x4000000)
|
||||
goto done;
|
||||
|
||||
ssfw_head = (void *)fw->data;
|
||||
|
|
|
@ -50,11 +50,6 @@ static inline u32 sigma_action_len(struct sigma_action *sa)
|
|||
return (sa->len_hi << 16) | sa->len;
|
||||
}
|
||||
|
||||
static inline size_t sigma_action_size(struct sigma_action *sa, u32 payload_len)
|
||||
{
|
||||
return sizeof(*sa) + payload_len + (payload_len % 2);
|
||||
}
|
||||
|
||||
extern int process_sigma_firmware(struct i2c_client *client, const char *name);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue