September 30, 2020

GHSL-2020-074, 077, 078: Memory corruptions in HPLIP - CVE-2020-6923

Kevin Backhouse

Summary

HPLIP contains two memory corruption vulnerabilities which can be triggered by a malicious device or computer that is connected to the same network. The vulnerabilities are triggered when an application such as simple-scan searches the network for scanners. In the specific case of simple-scan, this happens immediately when simple-scan starts, so there isn't even any need to trick the user into thinking that the scanner is genuine so that they will click on it.

We have also identified two lower-severity bugs in HPLIP, which we have also included in this report.

Product

HP Linux Imaging and Printing (HPLIP).

Tested Version

HPLIP 3.17.10+repack0-5, tested on Ubuntu 18.04.4 LTS with simple-scan 3.28.0-0ubuntu1.

Details

Issue 1 (GHSL-2020-074, CVE-2020-6923): Heap buffer overflow in mdns_readName

The function mdns_readName (protocol/discovery/mdns.c, line 177) is used during parsing of an MDNS message. The MDNS message is a response to a broadcast message that was sent out by mdns_probe_nw_scanners (protocol/discovery/mdns.c, line 426). Any device that is connected to the same network can receive the broadcast message and respond to it, so the response could come from a malicious device. The parsed name is written to buf, which is a pointer to a 256 byte buffer on the heap. There is no bounds checking in this function, so the malicious device can send a message that overflows the heap buffer.

static int mdns_readName(unsigned char* start, unsigned char *Response, char *buf)
{
    int size = 0;
    char *name = buf;
    unsigned char *p = Response;

    while (size = *p++)
    {
        if (size >= 0xC0)
        {
            //Compressed Size. Just ignore it.
            p++; //skip Offset byte
            return (p - Response);
        }
        memcpy(name, p, size);  <===== heap buffer overflow
        name[size] = '.';
        p += size;
        name += size + 1;
    }

    *(name - 1) = '\0';

    DBG("Name = [%s]\n", buf);
    return (p - Response);
}

We have included a proof-of-concept exploit for this vulnerability. Compile and run the PoC as follows:

g++ fakescanner.cpp -o fakescanner
./fakescanner hplip 0

Now run simple-scan on a different computer that is connected to the same network (ethernet or wifi):

simple-scan

You should see simple-scan crash with a segmentation fault.

Impact

This issue may lead to remote code execution, where "remote" means a device or computer connected to the same network as the victim. For example, in a typical office environment the malicious device would need to be somewhere inside the building.

Mitigations such as ASLR may make it difficult for an attacker to create a reliable RCE exploit for the vulnerability. The PoC that we have provided only causes HPLIP to crash.

Resources

We have published the source code for our PoC, fakescanner.cpp.

Issue 2 (GHSL-2020-077, CVE-2020-6923): Stack buffer overflow in mdns_update_uris

The function mdns_update_uris (protocol/discovery/mdns.c, line 381) is used to process a linked list of MDNS messages. The MDNS messages are the responses to a broadcast message that was sent out by mdns_probe_nw_scanners (protocol/discovery/mdns.c, line 426). Any device that is connected to the same network can receive the broadcast message and respond to it, so one of the responses could come from a malicious device. mdns_update_uris has a stack buffer overflow:

static int mdns_update_uris(DNS_RECORD *rr, char* uris_buf, int buf_size, int *count)
{
    char tempuri[MAX_URI_LEN] = {0};
    int bytes_read = 0;

    DBG("mdns_update_uris.\n");

    *count = 0;
    memset(uris_buf, 0, buf_size);

    while(rr)
    {
        if (rr->mdl[0] && rr->ip[0] /*&& strstr(rr->mdl, "scanjet")*/)
        {
            memset(tempuri, 0, sizeof(tempuri));
            sprintf(tempuri, "hp:/net/%s?ip=%s&queue=false", rr->mdl, rr->ip);   <===== stack buffer overflow

            //Check whether buffer has enough space to add new URI and check for duplicate URIs.
            if(bytes_read + sizeof(tempuri) < buf_size  && !strstr(uris_buf, tempuri))
            {
                bytes_read += sprintf(uris_buf + bytes_read, "%s;", tempuri);
                (*count)++;
                *(uris_buf + bytes_read) = '\0';
            }
        }
        rr = rr->next;
    }

    DBG("mdns_update_uris Count=[%d] bytes=[%d] URIs = %s\n",*count, bytes_read, uris_buf);
    return bytes_read;
}

A stack buffer overflow can occur if the combined length of rr->mdl and rr->ip leads to a uri string that is too big to fit in tempuri. Both of these strings are controlled by the attacker, because they are parsed from the MDNS message, during mdns_parse_respponse.

We have included a proof-of-concept exploit for this vulnerability. Compile and run the PoC as follows:

g++ fakescanner.cpp -o fakescanner
./fakescanner hplip 1

Now run simple-scan on a different computer that is connected to the same network (ethernet or wifi):

simple-scan

You should see simple-scan abort with a "stack smashing detected" error message.

Impact

This issue may lead to remote code execution, where "remote" means a device or computer connected to the same network as the victim. For example, in a typical office environment the malicious device would need to be somewhere inside the building.

Mitigations such as stack smashing detection may make it difficult for an attacker to create a reliable RCE exploit for the vulnerability. The PoC that we have provided only causes HPLIP to crash.

Resources

We have published the source code for our PoC, fakescanner.cpp.

Issue 3 (GHSL-2020-078, CVE-2020-6923): Out-of-bounds read in mdns_parse_respponse

The function mdns_parse_respponse (protocol/discovery/mdns.c, line 381) is used to parse an MDNS message. The MDNS message is a response to a broadcast message that was sent out by mdns_probe_nw_scanners (protocol/discovery/mdns.c, line 426). Any device that is connected to the same network can receive the broadcast message and respond to it, so the response could come from a malicious device. mdns_parse_respponse has an out-of-bounds read:

static void mdns_parse_respponse(unsigned char *Response, DNS_RECORD *rr)
{
    unsigned char *p = Response;
    unsigned short type = 0, data_len = 0;
    DNS_PKT_HEADER h;
    int i = 0;

    DBG("mdns_parse_respponse entry.\n");
    mdns_read_header(Response, &h);
    p += MDNS_HEADER_SIZE;

    for (i = 0; i < h.questions; i++)
    {
        p += mdns_readName(Response, p, rr->name);
        p += 4; //Skip TYPE(2 bytes)/CLASS(2 bytes)
    }

    for (i = 0; i < (h.answers + h.additionals); i++)
    {
        p += mdns_readName(Response, p, rr->name);  <===== (step 3) out-of-bounds read
        type = (*p << 8  | *(p+1));
        p += 8;  //Skip type(2 bytes)/class(2 bytes)/TTL(4 bytes)

        data_len = ( *p << 8  | *(p+1));  <===== (step 1) data_len is controlled by the attacker
        p += 2;  //Skip data_len(2 bytes)

        switch (type)
        {
            case QTYPE_A:
                sprintf(rr->ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
                break;
            case QTYPE_TXT:
                mdns_readMDL(p, rr->mdl, data_len);
                break;
            default:
                break;
        }

        p += data_len;  <===== (step 2) p can advance beyond the end of the buffer
        //DBG("TYPE = %d, Length = %d\n",type, data_len);
    }

    DBG("mdns_parse_respponse returning MDL = %s, IP = %s\n",rr->mdl, rr->ip);
}

We have included a proof-of-concept exploit for this vulnerability. Compile and run the PoC as follows:

g++ fakescanner.cpp -o fakescanner
./fakescanner hplip 2

Now run simple-scan with gdb on a different computer that is connected to the same network (ethernet or wifi):

$ gdb simple-scan
(gdb) break mdns_parse_respponse
(gdb) run

You need to run simple-scan with a debugger (gdb) to see the bug, because the out-of-bounds read does not cause a crash.

Impact

By itself, the severity of this issue is very low. However, it may be very useful to an attacker who is attempting to exploit one of the previous two issues, because it provides a way to read outside the buffer, which may enable the attacker to deduce information such as the ASLR offsets or the value of the stack canary.

Resources

We have published the source code for our PoC, fakescanner.cpp.

Issue 4: Stack buffer overflow in mdns_lookup

This issue is unrelated to the other issues, but we have included it in this report because the bug is in the same source file as the other bugs. The function mdns_lookup (protocol/discovery/mdns.c, line 462) has a stack buffer overflow:

int mdns_lookup(char* hostname, unsigned char* ip)
{
    int udp_socket = 0;
    int stat = MDNS_STATUS_ERROR;
    char fqdn[MAX_NAME_LENGTH] = {0};
    DNS_RECORD *rr_list = NULL;

    DBG("mdns_probe_nw_scanners entry.\n");
    /* Open UDP socket */
    if (mdns_open_socket(&udp_socket) != MDNS_STATUS_OK)
        goto bugout;

    /* Send dns query */
    sprintf(fqdn, "%s.local", hostname);  <===== stack buffer overflow
    mdns_send_query(udp_socket, fqdn, QTYPE_A);

    /* Read Responses */
    rr_list = mdns_read_responses(udp_socket, MODE_READ_SINGLE);

    /* Update IP Address buffer */
    if(rr_list)
    {
        strcpy(ip, rr_list->ip);
        stat = MDNS_STATUS_OK;
        DBG("IP = [%s].\n",ip);
    }

bugout:
    if (udp_socket >= 0)
        close(udp_socket);

    mdns_rr_cleanup(rr_list);
    return stat;
}

You can trigger the bug by running this command:

hp-makeuri -g xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Impact

As far as we are aware this issue does not have any security impact. That is because we are not aware of any scenario in which mdns_lookup is exposed to attacker-controlled data. Our PoC is a local attack, so it does not demonstrate an interesting attack surface.

CVE

  • CVE-2020-6923 (HP PSRT ID: PSR-2020-0077)

Coordinated Disclosure Timeline

  • 2020-04-20 reported: https://bugs.launchpad.net/hplip/+bug/1873901
  • 2020-05-05 no response on the bug report, so I used the main HP PSRT contact form to ask if they can chase the HPLIP team down.
  • 2020-05-05 Reply from HP PSRT, asking for more details. I sent them another copy of the report and PoC.
  • 2020-07-02 I emailed HP PSRT to remind them that the 90 day deadline expires on 2020-07-19.
  • 2020-07-06 Reply from HP PSRT. They have assigned this: PSR-2020-0077.
  • 2020-07-13 HP PSRT inform me that they have resolved the issue. They ask me to test and confirm.
  • 2020-07-13 I ask for the latest source code so that I can confirm the fix.
  • 2020-07-15 HP PSRT send me the latest source code.
  • 2020-07-15 I inform HP PSRT that the bugs have not been fixed.
  • 2020-07-16 HP PSRT confirm that they were mistaken and that the fix has not yet been released.
  • 2020-07-30 HP PSRT send me an updated version of the source code.
  • 2020-08-11 I confirm that the original bugs have been fixed, but also inform HP PSRT that some new bugs have been introduced.
  • 2020-08-21 HP PSRT send me an updated version of the source code.
  • 2020-08-21 I confirm that the bugs are fixed.
  • 2020-08-28 HP PSRT sets a disclosure date of 2020-09-30.
  • 2020-09-30 Vulnerability disclosed.

Resources

Credit

These issues were discovered and reported by GHSL team member @kevinbackhouse (Kevin Backhouse).

Contact

You can contact the GHSL team at securitylab@github.com, please include the relevant GHSL IDs in any communication regarding these issues.