oss-sec mailing list archives
Linux kernel: a heap buffer overflow in firedtv driver
From: "Luo Likang" <luolikang () nsfocus com>
Date: Tue, 20 Apr 2021 09:58:05 +0800
I found a buffer overflow vulnerability in function avc_ca_pmt(drivers/media/fireware/firedtv-avc.c). The bounds checking in avc_ca_pmt() is not strict enough. It should be checking "read_pos + 4" because it's reading 5 bytes. If the "es_info_length" is non-zero then it reads a 6th byte so there needs to be an additional check for that. a) static int fdtv_ca_pmt(struct firedtv *fdtv, void *arg) { struct ca_msg *msg = arg; int data_pos; int data_length; int i; data_pos = 4; if (msg->msg[3] & 0x80) { data_length = 0; for (i = 0; i < (msg->msg[3] & 0x7f); i++) data_length = (data_length << 8) + msg->msg[data_pos++]; } else { data_length = msg->msg[3]; } return avc_ca_pmt(fdtv, &msg->msg[data_pos], data_length); <== setp in } In this function, the content of `arg` is still the original data passed in by the user. If msg->msg[3] = 0x84, msg->[4] = 0xff, msg->msg[5]=0xff, msg->msg[6]=0xff, msg->msg[7]=0xff , will enter the for loop four times, and data_length will be equal to 0xffffffff, and then call avc_ca_pmt(fdtv, &msg->msg[8], 0xffffffff) b) int avc_ca_pmt(struct firedtv *fdtv, char *msg, int length) { .. int program_info_length; int pmt_cmd_id; int read_pos; int write_pos; int es_info_length; int crc32_csum; int ret; .. program_info_length = ((msg[4] & 0x0f) << 8) + msg[5]; ///////////// [0] if (program_info_length > 0) program_info_length--; /* Remove pmt_cmd_id */ pmt_cmd_id = msg[6]; //////////////[1] c->operand[0] = SFE_VENDOR_DE_COMPANYID_0; .. c->operand[23] = (program_info_length & 0xff); /* CA descriptors at programme level */ read_pos = 6; write_pos = 24; if (program_info_length > 0) { pmt_cmd_id = msg[read_pos++]; if (pmt_cmd_id != 1 && pmt_cmd_id != 4) ////////////[2] dev_err(fdtv->device, "invalid pmt_cmd_id %d\n", pmt_cmd_id); if (program_info_length > sizeof(c->operand) - 4 - write_pos) { ///[3] ret = -EINVAL; goto out; } memcpy(&c->operand[write_pos], &msg[read_pos], program_info_length); read_pos += program_info_length; write_pos += program_info_length; //////[4] } while (read_pos < length) { ////////[5] c->operand[write_pos++] = msg[read_pos++]; ///[6] c->operand[write_pos++] = msg[read_pos++]; c->operand[write_pos++] = msg[read_pos++]; .. } } In [0] and [1] : We can full control the program_info_length and pmt_cmd_id ; In [2] : for bypass it, pmt_cmd_id must be 1 or 4 ; In [3] : program_info_length > sizeof(c->operand) - 4 - write_pos) ==> program_info_length <= 509 - 4 - 24 = 0x1e1, so you can control its value to be 0x1e1 . In [4] : write_pos will be updated to 24+0x1e1 = 505 In [5]: at this time, length=0xffffffff, read_pos is much smaller than it, so in[6] will overwrite the c->oprand many bytes. Patch : https://lore.kernel.org/linux-media/YHaulytonFcW+lyZ@mwanda/ Credit : LuoLikang @ NSFOCUS SECURITY TEAM
Current thread:
- Linux kernel: a heap buffer overflow in firedtv driver Luo Likang (Apr 20)