I’ve recently discovered and reported a buffer overflow vulnerability inside the linux kernel, specifically in the SCSI driver – The sr_do_ioctl function in drivers/scsi/sr_ioctl.c in the Linux kernel through 4.16.12 allows local users to cause a denial of service or – in the rare case of a kernel that is compiled without the stack-protector – the vulnerability could allow further exploitation with kernel privileges.

This minimal code would be sufficient in order to trigger the vulnerability,and all that is needed is a CDROM, be it a physical device or a virtual one:

int main(void)
char buffer[6];
int res = open("/dev/sr0", O_RDWR | O_NONBLOCK);
ioctl(res, CDROMREADRAW, buffer);

Let’s deep dive into the technical details of this bug, and see how exactly this small piece of code would cause a nasty Denial of Service.

In order to fully understand the flow, we will follow the call trace starting from our call to ioctl() which we are calling with the /dev/sr0 CDROM file descriptor, the mode of operation – CDROMREADRAW, and a buffer that has no effect on the vulnerability and can be anything.

If we look at the code of ioctl, we see a few calls to various helper functions and then we reach the function mmc_ioctl_cdrom_read_data() which is located in /drivers/cdrom/cdrom.c:2941

static noinline int mmc_ioctl_cdrom_read_data(struct cdrom_device_info *cdi,void __user *arg, struct packet_command *cgc,int cmd)
struct request_sense sense;
struct cdrom_msf msf;
int blocksize = 0, format = 0, lba;
int ret;

memset(&sense, 0, sizeof(sense));
cgc->sense = &sense;
cgc->data_direction = CGC_DATA_READ;
ret = cdrom_read_block(cdi, cgc, lba, 1, format, blocksize);

Here we can see that struct request_sense is used for this function, and by digging around the /include/uapi/linux/cdrom.h header we can find that the struct is 64 bytes in size, later in the flow this request_sense struct is passed to cdrom_read_block() function as part of the cgc parameter.

At this point we will go trough some additional unrelated functions, and land onto /drivers/scsi/sr_ioctl.c in the call to the function sr_do_ioctl()

int sr_do_ioctl(Scsi_CD *cd, struct packet_command *cgc)
struct scsi_device *SDev;
struct scsi_sense_hdr sshdr;
int result, err = 0, retries = 0;
unsigned char sense_buffer[SCSI_SENSE_BUFFERSIZE], *senseptr = NULL;

SDev = cd->device;

if (cgc->sense)
senseptr = sense_buffer;

if (!scsi_block_when_processing_errors(SDev)) {
err = -ENODEV;
goto out;

result = scsi_execute(SDev, cgc->cmd, cgc->data_direction,
cgc->buffer, cgc->buflen, senseptr, &sshdr,
cgc->timeout, IOCTL_RETRIES, 0, 0, NULL);

In the beginning of this function we can see that the sense buffer is defined with the size of SCSI_SENSE_BUFFERSIZE, which is 96 bytes.

But as we remember, we have passed a 64 bytes sense struct during our flow. The problem here is that the CDROM struct request_sense is smaller than the SCSI sense buffer, and we are casting the former to the latter when we execute the following code inside of sr_do_ioctl() when we are ultimately calling to scsi_execute() :

result = scsi_execute(SDev, cgc->cmd, cgc->data_direction,
cgc->buffer, cgc->buflen,
(unsigned char *)cgc->sense, &sshdr,
cgc->timeout, IOCTL_RETRIES, 0, 0, NULL);

After that we will hit the final part that will blow the stack and crash the system, this part is located in /drivers/scsi/scsi_lib.c inside the function scsi_execute

On line 302 we can see this:

if (sense && rq->sense_len)
memcpy(sense, rq->sense,SCSI_SENSE_BUFFERSIZE);

Now as you remember, SCSI_SENSE_BUFFERSIZE is 96 bytes, but the target struct sense that we have passed along the flow is only 64 bytes, so when the memcpy will be executed the Linux kernel will panic as the stack will be blown.

Fortunately the stack-protector catches it, but that does not mean that the system will not crash.

The Linux security team were extremely fast and professional in handling this and they released a fix and backported it in less than a week after the report, you can check the patch over here and here.

Stay tuned for more @TwistlockLabs
Report Time: May 21th
Patch Time: May 25th