qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH] ide:atapi: check io_buffer_index in ide_atapi_cmd_reply_end


From: Wenxiang Qian
Subject: Re: [PATCH] ide:atapi: check io_buffer_index in ide_atapi_cmd_reply_end
Date: Fri, 11 Dec 2020 16:32:53 +0800

+ The lba is set to -1 to avoid some code paths, to make PoC simpler.

void ide_atapi_cmd_reply_end(IDEState *s)
{
    int byte_count_limit, size, ret;
    while (s->packet_transfer_size > 0) {
.....
        if (s->lba != -1 && s->io_buffer_index >= s->cd_sector_size) {   <----- set lba to -1 to avoid this part
 .....
        }
        if (s->elementary_transfer_size > 0) {
......
        } else {
.......
            if (s->lba != -1) {   <-----
                if (size > (s->cd_sector_size - s->io_buffer_index))
                    size = (s->cd_sector_size - s->io_buffer_index);  <-----
            }
        }

Wenxiang Qian <leonwxqian@gmail.com> 于2020年12月11日周五 下午4:23写道:
Hello,

I may not have made the detail clear in my previous email. The details of the AHCI device, after running the reproducer I attached in my report are as follows. If there is any information I can provide, please let me know. Thank you.

###root cause###
(1) The s->packet_transfer_size is bigger than the actual data.
(2) packet_transfer_size is passed into  ide_atapi_cmd_reply_end, as the total number of iterations. Each iterate round, s->io_buffer_index is increased by 2048, but without boundary check.
(3) The call to ide_transfer_start_norecurse use s->io_buffer + s->io_buffer_index - size as the index, cause an OOB access.

###details###
1. The reproducer sends a command of [WIN_PACKETCMD] + [CMD_READ] and value of IDE device's registers from guest to qemu. 

Callback ahci_port_write is called, then check_cmd is called.

2. The packet will set all the registers of the device via: handle_cmd --> handle_reg_h2d_fis.

Registers will be set here:

handle_reg_h2d_fis(..){
...
    ide_state->feature = cmd_fis[3];   //######[1]###### , cmd_fis is the data sent by the reproducer.
    ide_state->sector = cmd_fis[4];      /* LBA 7:0 */
    ide_state->lcyl = cmd_fis[5];        /* LBA 15:8  */
    ide_state->hcyl = cmd_fis[6];        /* LBA 23:16 */
    ide_state->select = cmd_fis[7];      /* LBA 27:24 (LBA28) */
    ide_state->hob_sector = cmd_fis[8];  /* LBA 31:24 */
    ide_state->hob_lcyl = cmd_fis[9];    /* LBA 39:32 */
    ide_state->hob_hcyl = cmd_fis[10];   /* LBA 47:40 */
    ide_state->hob_feature = cmd_fis[11];
    ide_state->nsector = (int64_t)((cmd_fis[13] << 8) | cmd_fis[12]);

 and ide_exec_cmd will be called to process [WIN_PACKETCMD] command.
     ide_exec_cmd(&s->dev[port].port, cmd_fis[2]);

3. ide_exec_cmd (core.c) handles the command,

    [WIN_PACKETCMD]               = { cmd_packet, CD_OK },

and make a call to cmd_packet,

cmd_packet(...) {
    ...

s->atapi_dma = s->feature & 1;  //######[2]######
    if (s->atapi_dma) {
        s->dma_cmd = IDE_DMA_ATAPI;
    }
    s->nsector = 1;
    ide_transfer_start(s, s->io_buffer, ATAPI_PACKET_SIZE,
                       ide_atapi_cmd);
    ...
}

and set the device to use PIO mode according to s->feature (set in Step 2->##[1]##).

Then, ide_transfer_start is called.
It will pass the [CMD_READ] part after [WIN_PACKETCMD] to ide_atapi_cmd.

4. ide_atapi_cmd parses [CMD_READ], and then calls the corresponding handler: cmd_read.

    [ 0x28 ] = { cmd_read, /* (10) */               CHECK_READY },

In cmd_read, the values of nb_sectors and lba are determined according to the packets passed by the reproducer.

In my PoC I set lba to -1 (0xfffffff) and nb_sectors to a large value, such as 0x800.


static void cmd_read(IDEState *s, uint8_t* buf)
{
    int nb_sectors, lba;

    if (buf[0] == GPCMD_READ_10) {
        nb_sectors = lduw_be_p(buf + 7);
    } else {
        nb_sectors = ldl_be_p(buf + 6);   //#####3#####
    }

    lba = ldl_be_p(buf + 2);   //######4######

....

    ide_atapi_cmd_read(s, lba, nb_sectors, 2048);
}

5. The code enters the ide_atapi_cmd_read -> ide_atapi_cmd_read_pio.

static void ide_atapi_cmd_read(.....)
{...
    if (s->atapi_dma) {
        ide_atapi_cmd_read_dma(s, lba, nb_sectors, sector_size);
    } else {
        ide_atapi_cmd_read_pio(s, lba, nb_sectors, sector_size);  //######5#######
    }
}

It will set the attributes according to the value passed in the previous steps.
The number of s->packet_transfer_size, which is the packet to read or write, nb_sectors * 2048 can be larger than the buffer pre-allocated by qemu (about 256KB).


static void ide_atapi_cmd_read_pio(IDEState *s, int lba, int nb_sectors,
                                   int sector_size)
{
    s->lba = lba;
    s->packet_transfer_size = nb_sectors * sector_size;  //########6#########
    s->elementary_transfer_size = 0;
    s->io_buffer_index = sector_size;
    s->cd_sector_size = sector_size;

    ide_atapi_cmd_reply_end(s);
}


6. In ide_atapi_cmd_reply_end, the data is processed according to packet_transfer_size.

void ide_atapi_cmd_reply_end(IDEState *s)
{
...
    while (s->packet_transfer_size > 0) {  //########7#######
....
        s->packet_transfer_size -= size;
        s->elementary_transfer_size -= size;
        s->io_buffer_index += size;  //#######8#######

        if (!ide_transfer_start_norecurse(s,
                                          s->io_buffer + s->io_buffer_index - size,  
                                          size, ide_atapi_cmd_reply_end)) {
            return;
        }


The "size" is usually 2048, which means the io_buffer_index increases by 2048 per round.

Qemu does not test if the result of this operation exceeds the length of the io_buffer itself, resulting in out-of-bounds access.

In ide_transfer_start_norecurse,

bool ide_transfer_start_norecurse(IDEState *s, uint8_t *buf, int size,
                                  EndTransferFunc *end_transfer_func)
{
    s->data_ptr = buf;         //s->io_buffer + s->io_buffer_index - size
    s->data_end = buf + size;  //data_ptr + 2048
....
    s->bus->dma->ops->pio_transfer(s->bus->dma);  //#######9########
    return true;
}

//####9####:
static const IDEDMAOps ahci_dma_ops = {
...
    .pio_transfer = ahci_pio_transfer,
...
};

In the final processing function ahci_pio_transfer:

static void ahci_pio_transfer(const IDEDMA *dma)
{
....

    uint32_t size = (uint32_t)(s->data_end - s->data_ptr);  // 2048, usually

uint16_t opts = le16_to_cpu(ad->cur_cmd->opts);  //####user controlled value#####
    int is_write = opts & AHCI_CMD_WRITE;            // read or write is decided by user.

.....


    if (has_sglist && size) {
        if (is_write) {
            dma_buf_write(s->data_ptr, size, &s->sg);   //##10##### both can be reached ####
        } else {
            dma_buf_read(s->data_ptr, size, &s->sg);    //##11##### both can be reached ####
        }
    }
}


s->data_ptr can be a value out of range, so base on ad->cur_cmd->opts,  ##10## ##11## can be OOB read or OOB write.

OOB read: obtain the leaked information, which can be used to bypass ASLR or obtain information about the host.
OOB write: which may overwrite some structs of the virtual device after it, overwrite the function pointers in the struct.

Best regards,
Wenxiang Qian

P J P <ppandit@redhat.com> 于2020年12月2日周三 下午9:17写道:
  Hi,

[doing a combined reply]

+-- On Tue, 1 Dec 2020, Philippe Mathieu-Daudé wrote --+
| Is it possible to release the reproducer to the community, so we can work on
| a fix and test it?

* No, we can not release/share reproducers on a public list.

* We can request reporters to do so by their volition.


| What happens to reproducers when a CVE is assigned, but the bug is marked as
| "out of the QEMU security boundary"?
|
+-- On Tue, 1 Dec 2020, Peter Maydell wrote --+
| Also, why are we assigning CVEs for bugs we don't consider security bugs?

* We need to mark these componets 'out of security scope' at the source level,
  rather than on each bug.

* Easiest could be to just have a list of such components in the git text
  file. At least till the time we device --security build and run time option
  discussed earlier.
  -> https://lists.nongnu.org/archive/html/qemu-devel/2020-07/msg04680.html

+-- On Tue, 1 Dec 2020, Paolo Bonzini wrote --+
| qtests are not just helpful.  Adding regression tests for bugs is a *basic*
| software engineering principle.  If you don't have time to write tests, you
| (or your organization) should find it.

* I've been doing the patch work out of my own interest.

* We generally rely on upstream/engineering for fix patches, because of our
  narrower understanding of the code base.

+-- On Wed, 2 Dec 2020, Markus Armbruster wrote --+
| Paolo Bonzini <pbonzini@redhat.com> writes:
| > you need at least to enclose the reproducer, otherwise you're posting a
| > puzzle not a patch. :)
|
| Indeed. Posting puzzles is a negative-sum game.]

* Agreed. I think the upcoming 'qemu-security' list may help in this regard. 
  As issue reports+reproducer details shall go there.

* Even then, we'll need to ask reporter's permission before sharing their
  reproducers on a public list OR with non-members.

* Best is if reporters share/release reproducers themselves. Maybe we can have
  a public git repository and they can send a PR to include their reproducers
  in the repository.

* That way multiple reproducers for the same issue can be held together.


Thank you.
--
Prasad J Pandit / Red Hat Product Security Team
8685 545E B54C 486B C6EB 271E E285 8B5A F050 DE8D

reply via email to

[Prev in Thread] Current Thread [Next in Thread]