Coordinated Disclosure Timeline

Summary

The library libslirp contains three uninitialized memory vulnerabilities that may allow an attacker to leak host memory into a guest

Product

libslirp

Tested Version

4.5.0 and dfe1229fc8f707f76b3f4d09078ab5e9b5817469 on the master branch.

Details

Issue 1: Invalid pointer initialization in bootp.c:bootp_input (GHSL-2021-078)

The function bootp_input handles requests for the bootp protocol from the guest. While processing a udp packet that is smaller than the size of the bootp_t structure (548 bytes) it uses memory from outside the working mbuf buffer. This leads to the leakage of 10 bytes of uninitialized heap memory to the guest.

In the following code snippet the function mtod is used without verifying the availability of enough bytes to properly cast the mbuf into a bootp_t type.

bootp.c

void bootp_input(struct mbuf *m)
{
    struct bootp_t *bp = mtod(m, struct bootp_t *);

    if (bp->bp_op == BOOTP_REQUEST) {
        bootp_reply(m->slirp, bp);
    }
}

An attacker that is in control of the guest can issue a small packet that will lead to the leakage of the following fields of the bootp_t type while preparing a response to the processed packet:

struct bootp_t {
    ...
    uint32_t bp_xid;
    ...
    uint8_t bp_hwaddr[16];
};

The fields bp_xid and bp_hwaddr are the only two fields of the incoming packet that are copied into the bootp reply packet as shown in the following snippet:

rbp->bp_op = BOOTP_REPLY;

// (1)
rbp->bp_xid = bp->bp_xid;
rbp->bp_htype = 1;
rbp->bp_hlen = 6;

// (2)
memcpy(rbp->bp_hwaddr, bp->bp_hwaddr, ETH_ALEN);

If we use a debugger we can inspect the contents of bp->bp_xid, at (1), and see that they are uninitialized (\xbe is the pattern that AddressSanitizer uses to initialize allocations):

p/x bp->bp_xid
(const uint32_t) $6 = 0xbebebebe

Again if we use a debugger we can inspect the contents of “bp->bp_hwaddr”, at (2), and see that they are uninitialized as well:

mem read &bp->bp_hwaddr[0] -c 16
0x61b000000844: be be be be be be be be be be be be be be be be  ................

Once the reply packet is built, it is sent via “udp_output” which ultimately leads to a call to “slirp_send_packet_all”.

void slirp_send_packet_all(Slirp *slirp, const void *buf, size_t len)
{
    ssize_t ret = slirp->cb->send_packet(buf, len, slirp->opaque);

    if (ret < 0) {
        g_critical("Failed to send packet, ret: %ld", (long)ret);
    } else if (ret < len) {
        DEBUG_ERROR("send_packet() didn't send all data: %ld < %lu", (long)ret,
                    (unsigned long)len);
    }
}

A quick inspection of the sent packet with a debugger shows that we are indeed leaking memory into the guest:

mem read buf -c 80
0x7ffeefbecca0: ff ff ff ff ff ff 52 55 0a 00 00 01 08 00 45 10  ......RU......E.
0x7ffeefbeccb0: 02 40 00 00 00 00 40 11 6e 9d 0a 00 00 01 ff ff  .@....@.n.......
0x7ffeefbeccc0: ff ff 00 43 00 44 02 2c 64 31 02 01 06 00 be be  ...C.D.,d1......
0x7ffeefbeccd0: be be 00 00 00 00 00 00 00 00 0a 00 00 03 0a 00  ................
0x7ffeefbecce0: 00 01 00 00 00 00 be be be be be be 00 00 00 00  ................

Impact

This issue may lead to host memory disclosure.

Issue 2: Invalid pointer initialization in udp6.c:udp6_input (GHSL-2021-079)

The function udp6_input handles requests for the udp protocol from the guest. While processing a udp packet that is smaller than the size of the udphdr structure it uses memory from outside the working mbuf buffer.

In the following code snippet the function mtod is used without verifying the availability of enough bytes to properly cast the mbuf into a udphdr type.

udp6.c

void udp6_input(struct mbuf *m)
{
    Slirp *slirp = m->slirp;
    struct ip6 *ip, save_ip;
    struct udphdr *uh;
    int iphlen = sizeof(struct ip6);
    int len;
    struct socket *so;
    struct sockaddr_in6 lhost;
    int hop_limit;

    DEBUG_CALL("udp6_input");
    DEBUG_ARG("m = %p", m);

    if (slirp->restricted) {
        goto bad;
    }

    ip = mtod(m, struct ip6 *);
    m->m_len -= iphlen;
    m->m_data += iphlen;

    // (1)
    uh = mtod(m, struct udphdr *);
    m->m_len += iphlen;
    m->m_data -= iphlen;
    ...
}

An attacker that is in control of the guest can issue a small IPv6 packet that will lead to the use of an udphdr that has been initialized with invalid memory.

struct udphdr {
    uint16_t uh_sport; /* source port */
    uint16_t uh_dport; /* destination port */
    int16_t uh_ulen; /* udp length */
    uint16_t uh_sum; /* udp checksum */
};

If we use a debugger we can inspect the contents of uh, at (1), and see that they are uninitialized (\xbe is the pattern that AddressSanitizer uses to initialize allocations):

p/x *uh
(udphdr) $1 = (uh_sport = 0xbebe, uh_dport = 0xbebe, uh_ulen = 0xbebe, uh_sum = 0xbebe)

Impact

This issue may lead to out of bound read access or indirect memory disclosure.

Issue 3: Invalid pointer initialization in tftp.c:tftp_input (GHSL-2021-080)

The function tftp_input handles requests for the tftp protocol from the guest. While processing a udp packet that is smaller than the size of the tftp_t structure it uses memory from outside the working mbuf buffer.

In the following code snippet the function mtod is used without verifying the availability of enough bytes to properly cast the mbuf into a tftp_t type.

tftp.c

void tftp_input(struct sockaddr_storage *srcsas, struct mbuf *m)
{
    // (1)
    struct tftp_t *tp = (struct tftp_t *)m->m_data;

    switch (ntohs(tp->tp_op)) {
    case TFTP_RRQ:
        tftp_handle_rrq(m->slirp, srcsas, tp, m->m_len);
        break;

    case TFTP_ACK:
        tftp_handle_ack(m->slirp, srcsas, tp, m->m_len);
        break;

    case TFTP_ERROR:
        tftp_handle_error(m->slirp, srcsas, tp, m->m_len);
        break;
    }
}

An attacker that is in control of the guest can issue a small packet that will lead to the use of an tftp_t that has been initialized with invalid memory.

struct tftp_t {
    struct udphdr udp;
    uint16_t tp_op;
    union {
        struct {
            uint16_t tp_block_nr;
            uint8_t tp_buf[TFTP_BLOCKSIZE_MAX];
        } tp_data;
        struct {
            uint16_t tp_error_code;
            uint8_t tp_msg[TFTP_BLOCKSIZE_MAX];
        } tp_error;
        char tp_buf[TFTP_BLOCKSIZE_MAX + 2];
    } x;
} SLIRP_PACKED;

If we use a debugger we can inspect the contents of tp, at (1), and see that they are uninitialized (\xbe is the pattern that AddressSanitizer uses to initialize allocations):

p/x *tp
(tftp_t) $0 = {
  udp = (uh_sport = 0x0000, uh_dport = 0x4500, uh_ulen = 0x0000, uh_sum = 0x0000)
  tp_op = 0xbebe
  x = {
    tp_data = (tp_block_nr = 0xbebe, tp_buf = "\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe ... \xbe\xbe"...)
    tp_error = (tp_error_code = 0xbebe, tp_msg = "\xbe\xbe\xbe\xbe\xbe\xbe\xbe ... \xbe\xbe"...)
    tp_buf = "\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe ... \xbe\xbe"...
  }
}

Impact

This issue may lead to out of bound read access or indirect memory disclosure.

Issue 4: Invalid pointer initialization in udp.c:udp_input (GHSL-2021-081)

The function udp_input handles requests for the udp protocol from the guest. While processing a udp packet that is smaller than the size of the udphdr structure it uses memory from outside the working mbuf buffer.

In the following code snippet the function mtod is used without verifying the availability of enough bytes to properly cast the mbuf into a udphdr type.

udp.c

void udp_input(register struct mbuf *m, int iphlen)
{
    Slirp *slirp = m->slirp;
    register struct ip *ip;
    register struct udphdr *uh;

    ...

    /*
     * Get IP and UDP header together in first mbuf.
     */
    ip = mtod(m, struct ip *);
    uh = (struct udphdr *)((char *)ip + iphlen);

    /*
     * Make mbuf data length reflect UDP length.
     * If not enough data to reflect UDP length, drop.
     */
    len = ntohs((uint16_t)uh->uh_ulen);

    ...
}

An attacker that is in control of the guest can issue a small packet that will lead to the use of an udphdr that has been initialized with invalid memory.

struct udphdr {
    uint16_t uh_sport; /* source port */
    uint16_t uh_dport; /* destination port */
    int16_t uh_ulen; /* udp length */
    uint16_t uh_sum; /* udp checksum */
};

If we use a debugger we can inspect the contents of uh, at (1), and see that they are uninitialized (\xbe is the pattern that AddressSanitizer uses to initialize allocations):

p/x *uh
(udphdr) $1 = (uh_sport = 0xbebe, uh_dport = 0xbebe, uh_ulen = 0xbebe, uh_sum = 0xbebe)

Impact

This issue may lead to out of bound read access or indirect memory disclosure.

CVE

Resources

Credit

This issue was discovered and reported by GHSL team member @agustingianni (Agustin Gianni).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to the corresponding GHSL-ID’s in any communication regarding this issue.