QEMU Internal: PCNET

Introduction

This post will give a basic introduction on how QEMU emulates a pcnet network card from the view of source code. In QEMU, pcnet-pci.c and pcnet.c are the most important two files that are related with pcnet network card emulation. From my point of view, pcnet-pci.c is for emulating the operation between the QEMU and pcnet device, including device initialization and device IO communication; pcnet.c is for emulating the operation between QEMU and the guest machine, including packet transmission and data processing.
This post post will pick part of the source code of QEMU for explaining the internal of QEMU.

Device Initialization

The first function invoked to initialize the pcnet network card is pcnet_class_init in pcnet-pci.c

static void pcnet_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);

    k->realize = pci_pcnet_realize;
    k->exit = pci_pcnet_uninit;
    k->romfile = "efi-pcnet.rom",
    k->vendor_id = PCI_VENDOR_ID_AMD;
    k->device_id = PCI_DEVICE_ID_AMD_LANCE;
    k->revision = 0x10;
    k->class_id = PCI_CLASS_NETWORK_ETHERNET;
    dc->reset = pci_reset;
    dc->vmsd = &vmstate_pci_pcnet;
    dc->props = pcnet_properties;
    set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
}

The calling sequence that initializes the PCIDeviceClass object is given below:

#0  pcnet_class_init at /qemu/hw/net/pcnet-pci.c:345
#1  type_initialize  at /qemu/qom/object.c:303
#2  object_class_foreach_tramp  at /qemu/qom/object.c:752
#3  g_hash_table_foreach at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#4  object_class_foreach at /qemu/qom/object.c:774
#5  object_class_get_list at /qemu/qom/object.c:807
#6  find_default_machine at /qemu/vl.c:1489
#7  main at /qemu/vl.c:3035

The second important initialization function is pci_pcnet_realize, which is used to initialise the structure PCNetState. More details on this structure will be given in next chapter.

static void pci_pcnet_realize(PCIDevice *pci_dev, Error **errp)
{
    PCIPCNetState *d = PCI_PCNET(pci_dev);
    PCNetState *s = &d->state;
    uint8_t *pci_conf;

#if 0
    printf("sizeof(RMD)=%d, sizeof(TMD)=%d\n",
        sizeof(struct pcnet_RMD), sizeof(struct pcnet_TMD));
#endif

    pci_conf = pci_dev->config;

    pci_set_word(pci_conf + PCI_STATUS,
                 PCI_STATUS_FAST_BACK | PCI_STATUS_DEVSEL_MEDIUM);

    pci_set_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID, 0x0);
    pci_set_word(pci_conf + PCI_SUBSYSTEM_ID, 0x0);

    pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin A */
    pci_conf[PCI_MIN_GNT] = 0x06;
    pci_conf[PCI_MAX_LAT] = 0xff;

    /* Handler for memory-mapped I/O */
    memory_region_init_io(&d->state.mmio, OBJECT(d), &pcnet_mmio_ops, s,
                          "pcnet-mmio", PCNET_PNPMMIO_SIZE);

    memory_region_init_io(&d->io_bar, OBJECT(d), &pcnet_io_ops, s, "pcnet-io",
                          PCNET_IOPORT_SIZE);
    pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &d->io_bar);

    pci_register_bar(pci_dev, 1, 0, &s->mmio);

    s->irq = pci_allocate_irq(pci_dev);
    s->phys_mem_read = pci_physical_memory_read;
    s->phys_mem_write = pci_physical_memory_write;
    s->dma_opaque = pci_dev;

    pcnet_common_init(DEVICE(pci_dev), s, &net_pci_pcnet_info);
}

The calling sequence that initializes the PCNetState object is given below:

#0  pci_pcnet_realize at /qemu/hw/net/pcnet-pci.c:282
#1  pci_qdev_realize at /qemu/hw/pci/pci.c:1842
#2  device_set_realized at /qemu/hw/core/qdev.c:1046
#3  property_set_bool at /qemu/qom/object.c:1667
#4  object_property_set at /qemu/qom/object.c:946
#5  object_property_set_qobject at /qemu/qom/qom-qobject.c:24
#6  object_property_set_bool at /qemu/qom/object.c:1015
#7  qdev_device_add at /qemu/qdev-monitor.c:599
#8  device_init_func at /qemu/vl.c:2300
#9  qemu_opts_foreach at /qemu/util/qemu-option.c:1089
#10 main at /qemu/vl.c:4532

IO Communication

Here I will introduce the effect of IO-related code in [1] to demonstrate how QEMU works.
Structure PCNetState provides the emulated registers in QEMU to perform different operations. In PCNetState structure, rap is served as an index register.

typedef struct PCNetState_st PCNetState;

struct PCNetState_st {
    NICState *nic;
    NICConf conf;
    QEMUTimer *poll_timer;
    int rap, isr, lnkst;
    uint32_t rdra, tdra;
    uint8_t prom[16];
    uint16_t csr[128];
    uint16_t bcr[32];
    int xmit_pos;
    uint64_t timer;
    MemoryRegion mmio;
    uint8_t buffer[4096];
    qemu_irq irq;
    void (*phys_mem_read)(void *dma_opaque, hwaddr addr,
                         uint8_t *buf, int len, int do_bswap);
    void (*phys_mem_write)(void *dma_opaque, hwaddr addr,
                          uint8_t *buf, int len, int do_bswap);
    void *dma_opaque;
    int tx_busy;
    int looptest;
};

In QEMU pcnet emulation, all IO operation will be processed through pcnet_ioport_read and ponet_ioport_write first, and then the values will be passed into functions in pcnet.c for further processing.

PCNET Reset

This is the first step in [1].

inl(PCNET_PORT + 0x18);
inw(PCNET_PORT + RST);

From the stack dump given below, we can infer that inl is for IO communication of 4 bytes and inw is for IO communication of 2 bytes.

#0  pcnet_ioport_read (opaque=0x5579fe68ea90, addr=24, size=4) at /qemu/hw/net/pcnet-pci.c:84
#1  memory_region_read_accessor (mr=0x5579fe691d48, addr=24, value=0x7f870ce4d368, size=4, shift=0, mask=4294967295, attrs=...) at /qemu/memory.c:399
#2  access_with_adjusted_size (addr=24, value=0x7f870ce4d368, size=4, access_size_min=1, access_size_max=4, access=0x5579fae6d6ed , mr=0x5579fe691d48, attrs=...) at /qemu/memory.c:506
#3  memory_region_dispatch_read1 (mr=0x5579fe691d48, addr=24, pval=0x7f870ce4d368, size=4, attrs=...) at /qemu/memory.c:1108
#4  memory_region_dispatch_read (mr=0x5579fe691d48, addr=24, pval=0x7f870ce4d368, size=4, attrs=...) at /qemu/memory.c:1139
#5  address_space_rw (as=0x5579fb6b6f20 <address>, addr=49432, attrs=..., buf=0x7f872fa36000 "", len=4, is_write=false) at  /qemu/exec.c:2478
#6  kvm_handle_io (port=49432, attrs=..., data=0x7f872fa36000, direction=0, size=4, count=1) at  /qemu/kvm-all.c:1680
#7  kvm_cpu_exec (cpu=0x5579fd119c70) at /qemu/kvm-all.c:1849
#8  qemu_kvm_cpu_thread_fn (arg=0x5579fd119c70) at  /qemu/cpus.c:979

Thread 3 "qemu-system-x86" hit Breakpoint 1, pcnet_ioport_read (opaque=0x5579fe68ea90, addr=20, size=2) at /home/dango/Security/qemu/hw/net/pcnet-pci.c:84
84	    PCNetState *d = opaque;
#0  pcnet_ioport_read (opaque=0x5579fe68ea90, addr=20, size=2) at /qemu/hw/net/pcnet-pci.c:84
#1  memory_region_read_accessor (mr=0x5579fe691d48, addr=20, value=0x7f870ce4d368, size=2, shift=0, mask=65535, attrs=...) at/qemu/memory.c:399
#2  access_with_adjusted_size (addr=20, value=0x7f870ce4d368, size=2, access_size_min=1, access_size_max=4, access=0x5579fae6d6ed , mr=0x5579fe691d48, attrs=...) at /qemu/memory.c:506
#3  memory_region_dispatch_read1 (mr=0x5579fe691d48, addr=20, pval=0x7f870ce4d368, size=2, attrs=...) at /qemu/memory.c:1108
#4  memory_region_dispatch_read (mr=0x5579fe691d48, addr=20, pval=0x7f870ce4d368, size=2, attrs=...) at /qemu/memory.c:1139
#5  address_space_rw (as=0x5579fb6b6f20 <address>, addr=49428, attrs=..., buf=0x7f872fa36000 "", len=2, is_write=false) at /Security/qemu/exec.c:2484
#6  kvm_handle_io (port=49428, attrs=..., data=0x7f872fa36000, direction=0, size=2, count=1) at /qemu/kvm-all.c:1680
#7  kvm_cpu_exec (cpu=0x5579fd119c70) at /qemu/kvm-all.c:1849
#8  qemu_kvm_cpu_thread_fn (arg=0x5579fd119c70) at /qemu/cpus.c:979

With a further step, we can see that the two IO calls will invoke pcnet_ioport_readl and pcnet_ioport_readw respectively. But the core part of these two functions are similar as below:

//pcnet_ioport_readw
switch (addr & 0x0f) {
case 0x00: /* RDP */
     val = pcnet_csr_readw(s, s->rap);
     break;
case 0x02:
     val = s->rap;
     break;
case 0x04:
     pcnet_s_reset(s);
     val = 0;
     break;
case 0x06:
     val = pcnet_bcr_readw(s, s->rap);
     break;
}
//pcnet_ioport_readl
switch (addr & 0x0f) {
case 0x00: /* RDP */
     val = pcnet_csr_readw(s, s->rap);
     break;
case 0x04:
     val = s->rap;
     break;
case 0x08:
     pcnet_s_reset(s);
     val = 0;
     break;
case 0x0c:
     val = pcnet_bcr_readw(s, s->rap);
     break;
}

During my experiment, only inw(PCNET_PORT+RST) will invoke pcnet_s_reset function, which sets all emulated registers to default values.

PCNET Set Swstyle

This is the second step in [1].

outw(58, PCNET_PORT + RAP);
outw(0x0102, PCNET_PORT + RDP);

Similar to the previous step, I can observe the parameters passed to pcnet_ioport_write.

#0  pcnet_ioport_write (opaque=0x5579fe68ea90, addr=18, data=58, size=2) at /qemu/hw/net/pcnet-pci.c:112
#1  memory_region_write_accessor (mr=0x5579fe691d48, addr=18, value=0x7f870ce4d2f8, size=2, shift=0, mask=65535, attrs=...) at /qemu/memory.c:450
#2  access_with_adjusted_size (addr=18, value=0x7f870ce4d2f8, size=2, access_size_min=1, access_size_max=4, access=0x5579fae6d8bc , mr=0x5579fe691d48, attrs=...) at /qemu/memory.c:506
#3  memory_region_dispatch_write (mr=0x5579fe691d48, addr=18, data=58, size=2, attrs=...) at /qemu/memory.c:1158
#4  address_space_rw (as=0x5579fb6b6f20 <address>, addr=49426, attrs=..., buf=0x7f872fa36000 ":", len=2, is_write=true) at /qemu/exec.c:2445
#5  kvm_handle_io (port=49426, attrs=..., data=0x7f872fa36000, direction=1, size=2, count=1) at /qemu/kvm-all.c:1680
#6  kvm_cpu_exec (cpu=0x5579fd119c70) at /qemu/kvm-all.c:1849
#7  qemu_kvm_cpu_thread_fn (arg=0x5579fd119c70) at /qemu/cpus.c:979


#0  0x00005579fb04e55f in pcnet_ioport_write (opaque=0x5579fe68ea90, addr=16, data=258, size=2) at /qemu/hw/net/pcnet-pci.c:112
#1  0x00005579fae6d92d in memory_region_write_accessor (mr=0x5579fe691d48, addr=16, value=0x7f870ce4d2f8, size=2, shift=0, mask=65535, attrs=...) at /qemu/memory.c:450
#2  0x00005579fae6dac9 in access_with_adjusted_size (addr=16, value=0x7f870ce4d2f8, size=2, access_size_min=1, access_size_max=4, access=0x5579fae6d8bc , mr=0x5579fe691d48, attrs=...) at /qemu/memory.c:506
#3  0x00005579fae70374 in memory_region_dispatch_write (mr=0x5579fe691d48, addr=16, data=258, size=2, attrs=...) at /qemu/memory.c:1158
#4  0x00005579fae24939 in address_space_rw (as=0x5579fb6b6f20 <address>, addr=49424, attrs=..., buf=0x7f872fa36000 "\002\001\377\377", len=2, is_write=true) at /qemu/exec.c:2445
#5  0x00005579fae6abf4 in kvm_handle_io (port=49424, attrs=..., data=0x7f872fa36000, direction=1, size=2, count=1) at /qemu/kvm-all.c:1680
#6  0x00005579fae6b0e0 in kvm_cpu_exec (cpu=0x5579fd119c70) at /qemu/kvm-all.c:1849
#7  0x00005579fae52c0e in qemu_kvm_cpu_thread_fn (arg=0x5579fd119c70) at /qemu/cpus.c:979

Since both functions are outw, only pcnet_ioport_writew will be called.

if (!BCR_DWIO(s)) {
   switch (addr & 0x0f) {
   case 0x00: /* RDP */
        pcnet_csr_writew(s, s->rap, val);
        break;
   case 0x02:
        s->rap = val & 0x7f;
        break;
   case 0x06:
        pcnet_bcr_writew(s, s->rap, val);
        break;
   }
}

For function outw(58, PCNET_PORT + RAP), s->rap will be set to 58.
For function outw(), it will call pcnet_bcr_writew finally and set s->bcr[BCR_SWS] to desired value.

case 58:
    pcnet_bcr_writew(s,BCR_SWS,val);

static void pcnet_bcr_writew(PCNetState *s, uint32_t rap, uint32_t val)
{
    rap &= 127;
#ifdef PCNET_DEBUG_BCR
    printf("pcnet_bcr_writew rap=%d val=0x%04x\n", rap, val);
#endif
    switch (rap) {
    case BCR_SWS:
        if (!(CSR_STOP(s) || CSR_SPND(s)))
            return;
        val &= ~0x0300;
        switch (val & 0x00ff) {
        case 0:
            val |= 0x0200;
            break;
        case 1:
            val |= 0x0100;
            break;
        case 2:
        case 3:
            val |= 0x0300;
            break;
        default:
            printf("Bad SWSTYLE=0x%02x\n", val & 0xff);
            val = 0x0200;
            break;
        }
#ifdef PCNET_DEBUG
       printf("BCR_SWS=0x%04x\n", val);
#endif
    /* fall through */
    case BCR_LNKST:
    case BCR_LED1:
    case BCR_LED2:
    case BCR_LED3:
    case BCR_MC:
    case BCR_FDC:
    case BCR_BSBC:
    case BCR_EECAS:
    case BCR_PLAT:
        s->bcr[rap] = val;
        break;
    default:
        break;
    }
}

/*
Thread 3 "qemu-system-x86" hit Breakpoint 3, pcnet_bcr_writew (s=0x5579fe68ea90, rap=20, val=770) at /home/dango/Security/qemu/hw/net/pcnet.c:1524
1524	        s->bcr[rap] = val;
(gdb) p/x val
$3 = 0x302
(gdb) p/x rap
$4 = 0x14
*/

Set CSR

With the knowledge given above, we come to the next step

outw(1, PCNET_PORT + RAP);
outw(lo, PCNET_PORT + RDP);
outw(2, PCNET_PORT + RAP);
outw(hi, PCNET_PORT + RDP);

We can observe that, s->csr[1] is set to lo and s->csr[2] is set to hi.

Start and Init

outw(0, PCNET_PORT + RAP);
outw(0x3, PCNET_PORT + RDP);

In the first function, s->rap is to 0 and consecutively call pcnet_init and pcnet_start in function pcnet_csr_writew.

Send Packet

outw(0, PCNET_PORT + RAP);
outw(0x8, PCNET_PORT + RDP);

In the first function, s->rap is to 0 and consecutively call pcnet_transmit in function pcnet_csr_writew.
The vulnerability CVE-2015-7504 exists in pcnet_receive function.

Conclusion

In this post, I write some notes on the source code of QEMU implementation. I mainly introduce the functions that involve processing the operation between running application in guest machine and QEMU emulator. For those function, I also list the stack dump for a better understanding on that.

Reference

[1] https://github.com/dangokyo/QEMU_ESCAPE/blob/master/CVE_2015_7504_crash.c

One thought on “QEMU Internal: PCNET

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.