Commit message: msf2-mac: switch to use qemu_receive_packet() for loopback

This patch switches to use qemu_receive_packet() which can detect
reentrancy and return early.

This is intended to address CVE-2021-3416.

Cc: Prasad J Pandit <ppandit@redhat.com>
Cc: qemu-stable@nongnu.org
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
CVE: CVE-2021-3416
CWE: CWE-835
Vulnerability description: A potential stack overflow via infinite loop issue was found in various NIC emulators of QEMU in versions up to and including 5.2.0. The issue occurs in loopback mode of a NIC wherein reentrant DMA checks get bypassed. A guest user/process may use this flaw to consume CPU cycles or crash the QEMU process on the host resulting in DoS scenario.

Project: qemu
File: msf2-emac.c

static void write_to_phy(MSF2EmacState *s)
{
    uint8_t reg_addr = s->regs[R_MII_ADDR] & R_MII_ADDR_REGADDR_MASK;
    uint8_t phy_addr = (s->regs[R_MII_ADDR] >> R_MII_ADDR_PHYADDR_SHIFT) &
                       R_MII_ADDR_REGADDR_MASK;
    uint16_t data = s->regs[R_MII_CTL] & 0xFFFF;

    if (phy_addr != PHYADDR) {
        return;
    }

    switch (reg_addr) {
    case MII_BMCR:
        if (data & MII_BMCR_RESET) {
            /* Phy reset */
            msf2_phy_reset(s);
            data &= ~MII_BMCR_RESET;
        }
        if (data & MII_BMCR_AUTOEN) {
            /* Complete autonegotiation immediately */
            data &= ~MII_BMCR_AUTOEN;
            s->phy_regs[MII_BMSR] |= MII_BMSR_AN_COMP;
        }
        break;
    }

    s->phy_regs[reg_addr] = data;
}

Project: qemu
File: msf2-emac.c

static void msf2_emac_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = msf2_emac_realize;
    dc->reset = msf2_emac_reset;
    dc->vmsd = &vmstate_msf2_emac;
    device_class_set_props(dc, msf2_emac_properties);
}

Project: qemu
File: msf2-emac.c

static void msf2_emac_do_reset(MSF2EmacState *s)
{
    memset(&s->regs[0], 0, sizeof(s->regs));
    s->regs[R_CFG1] = 0x80000000;
    s->regs[R_CFG2] = 0x00007000;
    s->regs[R_IFG] = 0x40605060;
    s->regs[R_HALF_DUPLEX] = 0x00A1F037;
    s->regs[R_MAX_FRAME_LENGTH] = 0x00000600;
    s->regs[R_FIFO_CFG5] = 0X3FFFF;

    msf2_phy_reset(s);
}

Project: qemu
File: msf2-emac.c

static void emac_set_link(NetClientState *nc)
{
    MSF2EmacState *s = qemu_get_nic_opaque(nc);

    msf2_phy_update_link(s);
}

Project: qemu
File: msf2-emac.c

static bool emac_can_rx(NetClientState *nc)
{
    MSF2EmacState *s = qemu_get_nic_opaque(nc);

    return (s->regs[R_CFG1] & R_CFG1_RX_EN_MASK) &&
           (s->regs[R_DMA_RX_CTL] & R_DMA_RX_CTL_EN_MASK);
}

Project: qemu
File: msf2-emac.c

static void emac_store_desc(MSF2EmacState *s, EmacDesc *d, hwaddr desc)
{
    /* Convert from host endianness into LE. */
    d->pktaddr = cpu_to_le32(d->pktaddr);
    d->pktsize = cpu_to_le32(d->pktsize);
    d->next = cpu_to_le32(d->next);

    address_space_write(&s->dma_as, desc, MEMTXATTRS_UNSPECIFIED, d, sizeof *d);
}

Project: qemu
File: msf2-emac.c

static void msf2_emac_realize(DeviceState *dev, Error **errp)
{
    MSF2EmacState *s = MSS_EMAC(dev);

    if (!s->dma_mr) {
        error_setg(errp, "MSS_EMAC 'ahb-bus' link not set");
        return;
    }

    address_space_init(&s->dma_as, s->dma_mr, "emac-ahb");

    qemu_macaddr_default_if_unset(&s->conf.macaddr);
    s->nic = qemu_new_nic(&net_msf2_emac_info, &s->conf,
                          object_get_typename(OBJECT(dev)), dev->id, s);
    qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
}

Project: qemu
File: msf2-emac.c

static void msf2_emac_init(Object *obj)
{
    MSF2EmacState *s = MSS_EMAC(obj);

    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);

    memory_region_init_io(&s->mmio, obj, &emac_ops, s,
                          "msf2-emac", R_MAX * 4);
    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
}

Project: qemu
File: msf2-emac.c

static void msf2_phy_reset(MSF2EmacState *s)
{
    memset(&s->phy_regs[0], 0, sizeof(s->phy_regs));
    s->phy_regs[MII_BMCR] = 0x1140;
    s->phy_regs[MII_BMSR] = 0x7968;
    s->phy_regs[MII_PHYID1] = 0x0022;
    s->phy_regs[MII_PHYID2] = 0x1550;
    s->phy_regs[MII_ANAR] = 0x01E1;
    s->phy_regs[MII_ANLPAR] = 0xCDE1;

    msf2_phy_update_link(s);
}

Project: qemu
File: msf2-emac.c

static void msf2_phy_update_link(MSF2EmacState *s)
{
    /* Autonegotiation status mirrors link status. */
    if (qemu_get_queue(s->nic)->link_down) {
        s->phy_regs[MII_BMSR] &= ~(MII_BMSR_AN_COMP |
                                   MII_BMSR_LINK_ST);
    } else {
        s->phy_regs[MII_BMSR] |= (MII_BMSR_AN_COMP |
                                  MII_BMSR_LINK_ST);
    }
}

Project: qemu
File: msf2-emac.c

static void msf2_emac_reset(DeviceState *dev)
{
    MSF2EmacState *s = MSS_EMAC(dev);

    msf2_emac_do_reset(s);
}

Project: qemu
File: msf2-emac.c

static void emac_write(void *opaque, hwaddr addr, uint64_t val64,
        unsigned int size)
{
    MSF2EmacState *s = opaque;
    uint32_t value = val64;
    uint32_t enreqbits;
    uint8_t pktcnt;

    addr >>= 2;
    switch (addr) {
    case R_DMA_TX_CTL:
        s->regs[addr] = value;
        if (value & R_DMA_TX_CTL_EN_MASK) {
            msf2_dma_tx(s);
        }
        break;
    case R_DMA_RX_CTL:
        s->regs[addr] = value;
        if (value & R_DMA_RX_CTL_EN_MASK) {
            s->rx_desc = s->regs[R_DMA_RX_DESC];
            qemu_flush_queued_packets(qemu_get_queue(s->nic));
        }
        break;
    case R_CFG1:
        s->regs[addr] = value;
        if (value & R_CFG1_RESET_MASK) {
            msf2_emac_do_reset(s);
        }
        break;
    case R_FIFO_CFG0:
       /*
        * For our implementation, turning on modules is instantaneous,
        * so the states requested via the *ENREQ bits appear in the
        * *ENRPLY bits immediately. Also the reset bits to reset PE-MCXMAC
        * module are not emulated here since it deals with start of frames,
        * inter-packet gap and control frames.
        */
        enreqbits = extract32(value, 8, 5);
        s->regs[addr] = deposit32(value, 16, 5, enreqbits);
        break;
    case R_DMA_TX_DESC:
        if (value & 0x3) {
            qemu_log_mask(LOG_GUEST_ERROR, "Tx Descriptor address should be"
                          " 32 bit aligned\n");
        }
        /* Ignore [1:0] bits */
        s->regs[addr] = value & ~3;
        break;
    case R_DMA_RX_DESC:
        if (value & 0x3) {
            qemu_log_mask(LOG_GUEST_ERROR, "Rx Descriptor address should be"
                          " 32 bit aligned\n");
        }
        /* Ignore [1:0] bits */
        s->regs[addr] = value & ~3;
        break;
    case R_DMA_TX_STATUS:
        if (value & R_DMA_TX_STATUS_UNDERRUN_MASK) {
            s->regs[addr] &= ~R_DMA_TX_STATUS_UNDERRUN_MASK;
        }
        if (value & R_DMA_TX_STATUS_PKT_SENT_MASK) {
            pktcnt = FIELD_EX32(s->regs[addr], DMA_TX_STATUS, PKTCNT);
            pktcnt--;
            s->regs[addr] = FIELD_DP32(s->regs[addr], DMA_TX_STATUS,
                                       PKTCNT, pktcnt);
            if (pktcnt == 0) {
                s->regs[addr] &= ~R_DMA_TX_STATUS_PKT_SENT_MASK;
            }
        }
        break;
    case R_DMA_RX_STATUS:
        if (value & R_DMA_RX_STATUS_OVERFLOW_MASK) {
            s->regs[addr] &= ~R_DMA_RX_STATUS_OVERFLOW_MASK;
        }
        if (value & R_DMA_RX_STATUS_PKT_RCVD_MASK) {
            pktcnt = FIELD_EX32(s->regs[addr], DMA_RX_STATUS, PKTCNT);
            pktcnt--;
            s->regs[addr] = FIELD_DP32(s->regs[addr], DMA_RX_STATUS,
                                       PKTCNT, pktcnt);
            if (pktcnt == 0) {
                s->regs[addr] &= ~R_DMA_RX_STATUS_PKT_RCVD_MASK;
            }
        }
        break;
    case R_DMA_IRQ:
        break;
    case R_MII_CMD:
        if (value & R_MII_CMD_READ_MASK) {
            s->regs[R_MII_STS] = read_from_phy(s);
        }
        break;
    case R_MII_CTL:
        s->regs[addr] = value;
        write_to_phy(s);
        break;
    case R_STA1:
        s->regs[addr] = value;
       /*
        * R_STA1 [31:24] : octet 1 of mac address
        * R_STA1 [23:16] : octet 2 of mac address
        * R_STA1 [15:8] : octet 3 of mac address
        * R_STA1 [7:0] : octet 4 of mac address
        */
        stl_be_p(s->mac_addr, value);
        break;
    case R_STA2:
        s->regs[addr] = value;
       /*
        * R_STA2 [31:24] : octet 5 of mac address
        * R_STA2 [23:16] : octet 6 of mac address
        */
        stw_be_p(s->mac_addr + 4, value >> 16);
        break;
    default:
        if (addr >= ARRAY_SIZE(s->regs)) {
            qemu_log_mask(LOG_GUEST_ERROR,
                          "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__,
                          addr * 4);
            return;
        }
        s->regs[addr] = value;
        break;
    }
    emac_update_irq(s);
}

Project: qemu
File: msf2-emac.c

static bool addr_filter_ok(MSF2EmacState *s, const uint8_t *buf)
{
    /* The broadcast MAC address: FF:FF:FF:FF:FF:FF */
    const uint8_t broadcast_addr[] = { 0xFF, 0xFF, 0xFF, 0xFF,
                                              0xFF, 0xFF };
    bool bcast_en = true;
    bool mcast_en = true;

    if (s->regs[R_FIFO_CFG5] & R_FIFO_CFG5_BCAST_MASK) {
        bcast_en = true; /* Broadcast dont care for drop circuitry */
    } else if (s->regs[R_FIFO_CFG4] & R_FIFO_CFG4_BCAST_MASK) {
        bcast_en = false;
    }

    if (s->regs[R_FIFO_CFG5] & R_FIFO_CFG5_MCAST_MASK) {
        mcast_en = true; /* Multicast dont care for drop circuitry */
    } else if (s->regs[R_FIFO_CFG4] & R_FIFO_CFG4_MCAST_MASK) {
        mcast_en = false;
    }

    if (!memcmp(buf, broadcast_addr, sizeof(broadcast_addr))) {
        return bcast_en;
    }

    if (buf[0] & 1) {
        return mcast_en;
    }

    return !memcmp(buf, s->mac_addr, sizeof(s->mac_addr));
}

Project: qemu
File: msf2-emac.c

static uint32_t emac_get_isr(MSF2EmacState *s)
{
    uint32_t ier = s->regs[R_DMA_IRQ_MASK];
    uint32_t tx = s->regs[R_DMA_TX_STATUS] & 0xF;
    uint32_t rx = s->regs[R_DMA_RX_STATUS] & 0xF;
    uint32_t isr = (rx << 4) | tx;

    s->regs[R_DMA_IRQ] = ier & isr;
    return s->regs[R_DMA_IRQ];
}

Project: qemu
File: msf2-emac.c

static void msf2_emac_register_types(void)
{
    type_register_static(&msf2_emac_info);
}

Project: qemu
File: msf2-emac.c

static void emac_load_desc(MSF2EmacState *s, EmacDesc *d, hwaddr desc)
{
    address_space_read(&s->dma_as, desc, MEMTXATTRS_UNSPECIFIED, d, sizeof *d);
    /* Convert from LE into host endianness. */
    d->pktaddr = le32_to_cpu(d->pktaddr);
    d->pktsize = le32_to_cpu(d->pktsize);
    d->next = le32_to_cpu(d->next);
}

Project: qemu
File: msf2-emac.c

static void msf2_dma_tx(MSF2EmacState *s)
{
    NetClientState *nc = qemu_get_queue(s->nic);
    hwaddr desc = s->regs[R_DMA_TX_DESC];
    uint8_t buf[MAX_PKT_SIZE];
    EmacDesc d;
    int size;
    uint8_t pktcnt;
    uint32_t status;

    if (!(s->regs[R_CFG1] & R_CFG1_TX_EN_MASK)) {
        return;
    }

    while (1) {
        emac_load_desc(s, &d, desc);
        if (d.pktsize & EMPTY_MASK) {
            break;
        }
        size = d.pktsize & PKT_SIZE;
        address_space_read(&s->dma_as, d.pktaddr, MEMTXATTRS_UNSPECIFIED,
                           buf, size);
        /*
         * This is very basic way to send packets. Ideally there should be
         * a FIFO and packets should be sent out from FIFO only when
         * R_CFG1 bit 0 is set.
         */
        if (s->regs[R_CFG1] & R_CFG1_LB_EN_MASK) {
            qemu_receive_packet(nc, buf, size);
        } else {
            qemu_send_packet(nc, buf, size);
        }
        d.pktsize |= EMPTY_MASK;
        emac_store_desc(s, &d, desc);
        /* update sent packets count */
        status = s->regs[R_DMA_TX_STATUS];
        pktcnt = FIELD_EX32(status, DMA_TX_STATUS, PKTCNT);
        pktcnt++;
        s->regs[R_DMA_TX_STATUS] = FIELD_DP32(status, DMA_TX_STATUS,
                                              PKTCNT, pktcnt);
        s->regs[R_DMA_TX_STATUS] |= R_DMA_TX_STATUS_PKT_SENT_MASK;
        desc = d.next;
    }
    s->regs[R_DMA_TX_STATUS] |= R_DMA_TX_STATUS_UNDERRUN_MASK;
    s->regs[R_DMA_TX_CTL] &= ~R_DMA_TX_CTL_EN_MASK;
}

Project: qemu
File: msf2-emac.c

static uint16_t read_from_phy(MSF2EmacState *s)
{
    uint8_t reg_addr = s->regs[R_MII_ADDR] & R_MII_ADDR_REGADDR_MASK;
    uint8_t phy_addr = (s->regs[R_MII_ADDR] >> R_MII_ADDR_PHYADDR_SHIFT) &
                       R_MII_ADDR_REGADDR_MASK;

    if (phy_addr == PHYADDR) {
        return s->phy_regs[reg_addr];
    } else {
        return 0xFFFF;
    }
}

Project: qemu
File: msf2-emac.c

static ssize_t emac_rx(NetClientState *nc, const uint8_t *buf, size_t size)
{
    MSF2EmacState *s = qemu_get_nic_opaque(nc);
    EmacDesc d;
    uint8_t pktcnt;
    uint32_t status;

    if (size > (s->regs[R_MAX_FRAME_LENGTH] & 0xFFFF)) {
        return size;
    }
    if (!addr_filter_ok(s, buf)) {
        return size;
    }

    emac_load_desc(s, &d, s->rx_desc);

    if (d.pktsize & EMPTY_MASK) {
        address_space_write(&s->dma_as, d.pktaddr, MEMTXATTRS_UNSPECIFIED,
                            buf, size & PKT_SIZE);
        d.pktsize = size & PKT_SIZE;
        emac_store_desc(s, &d, s->rx_desc);
        /* update received packets count */
        status = s->regs[R_DMA_RX_STATUS];
        pktcnt = FIELD_EX32(status, DMA_RX_STATUS, PKTCNT);
        pktcnt++;
        s->regs[R_DMA_RX_STATUS] = FIELD_DP32(status, DMA_RX_STATUS,
                                              PKTCNT, pktcnt);
        s->regs[R_DMA_RX_STATUS] |= R_DMA_RX_STATUS_PKT_RCVD_MASK;
        s->rx_desc = d.next;
    } else {
        s->regs[R_DMA_RX_CTL] &= ~R_DMA_RX_CTL_EN_MASK;
        s->regs[R_DMA_RX_STATUS] |= R_DMA_RX_STATUS_OVERFLOW_MASK;
    }
    emac_update_irq(s);
    return size;
}

Project: qemu
File: msf2-emac.c

static void emac_update_irq(MSF2EmacState *s)
{
    bool intr = emac_get_isr(s);

    qemu_set_irq(s->irq, intr);
}

Project: qemu
File: msf2-emac.c

static uint64_t emac_read(void *opaque, hwaddr addr, unsigned int size)
{
    MSF2EmacState *s = opaque;
    uint32_t r = 0;

    addr >>= 2;

    switch (addr) {
    case R_DMA_IRQ:
        r = emac_get_isr(s);
        break;
    default:
        if (addr >= ARRAY_SIZE(s->regs)) {
            qemu_log_mask(LOG_GUEST_ERROR,
                          "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__,
                          addr * 4);
            return r;
        }
        r = s->regs[addr];
        break;
    }
    return r;
}