Bug 1665371 (CVE-2019-3813)

Summary: CVE-2019-3813 spice: Off-by-one error in array access in spice/server/memslot.c
Product: [Other] Security Response Reporter: Andrej Nemec <anemec>
Component: vulnerabilityAssignee: Red Hat Product Security <security-response-team>
Status: CLOSED ERRATA QA Contact:
Severity: high Docs Contact:
Priority: high    
Version: unspecifiedCC: alon, bmcclain, cfergeau, cshao, dblechte, dfediuck, dmoppert, eedri, hdegoede, jforbes, kraxel, marcandre.lureau, mgoldboi, michal.skrivanek, mkenneth, rh-spice-bugs, sandmann, sbonazzo, security-response-team, sgayou, sherold, victortoso, yturgema
Target Milestone: ---Keywords: Security
Target Release: ---   
Hardware: All   
OS: Linux   
Whiteboard:
Fixed In Version: spice 0.14.2 Doc Type: If docs needed, set a value
Doc Text:
Spice, versions 0.5.2 through 0.14.1, are vulnerable to an out-of-bounds read due to an off-by-one error in memslot_get_virt. This may lead to a denial of service, or, in the worst case, code-execution by unauthenticated attackers.
Story Points: ---
Clone Of: Environment:
Last Closed: 2019-01-31 20:08:31 UTC Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Bug Depends On: 1666830, 1666831, 1669226, 1670158, 1670159, 1672153, 1672154    
Bug Blocks: 1663867    

Description Andrej Nemec 2019-01-11 07:35:53 UTC
An off-by-one error was found in spice when accessing arrays. A malicious guest user can use this for a host denial of service.

Comment 4 Scott Gayou 2019-01-14 23:47:03 UTC
Does look like it can change program control flow assuming checks can be bypassed, an attacker can control the heap information past the end of the buffer, etc. memslot_get_virt looks to return chunks of memory back to callers that are then cast into different structs and used. Thus, information leaks may be possible, lots of other super-undefined behavior could potentially occur (i.e. code execution)

```
   │1426        qxl = (QXLCursor *)memslot_get_virt(slots, addr, sizeof(*qxl), group_id, &error);                                            │
  >│1427        if (error) {                                                                                                                 │
   │1428            return false;                                                                                                            │
   │1429        }                                                                                                                            │
   │1430                                                                                                                                     │
   │1431        red->header.unique     = qxl->header.unique;                                                                                 │
   │1432        red->header.type       = qxl->header.type;                                                                                   │
```

another example of a use of memslot_get_virt:

```
   │1463    bool red_get_cursor_cmd(RedMemSlotInfo *slots, int group_id,                                                                     │
   │1464                            RedCursorCmd *red, QXLPHYSICAL addr)                                                                     │
   │1465    {                                                                                                                                │
   │1466        QXLCursorCmd *qxl;                                                                                                           │
   │1467        int error;                                                                                                                   │
   │1468                                                                                                                                     │
  >│1469        qxl = (QXLCursorCmd *)memslot_get_virt(slots, addr, sizeof(*qxl), group_id, &error);                                         │
   │1470        if (error) {                                                                                                                 │
   │1471            return false;                                                                                                            │
   │1472        }                                                                                                                            │
   │1473        red->release_info_ext.info      = &qxl->release_info;                                                                        │
   │1474        red->release_info_ext.group_id  = group_id;  
```

For an attacker to control the output memslot_get_virt, it looks like they need at least the following to occur:
1. Control group_id or the QXLPHYSICAL addr. Reporter indicated group_id may be unlikely, but QXLPHYSICAL might be possible.
2. If addr is controlled, set higher byte to slot_id such that "return addr >> info->memslot_id_shift" returns a number equal to info->num_memslots.
3. Control the heap such that the now out-of-bounds read of &info->mem_slots[group_id][slot_id] allows some control of the structure.
Attacker would then potentially be able to modify the following struct:

```c
typedef struct MemSlot {
    int generation;
    unsigned long virt_start_addr;
    unsigned long virt_end_addr;
    long address_delta;
} MemSlot;
```
4. slot->generation would need to be set to what memslot_get_generation returns, which I think is just more information encoded into the addr.
5. h_virt could then potentially be set to anything via:
```
h_virt += slot->address_delta;
```
6. memslot_validate_virt would need to be bypassed. With full control of the heap, I believe that wouldn't pose a problem. See the following line:
```c
if (virt < slot->virt_start_addr || (virt + add_size) > slot->virt_end_addr) {
```

After all of these hypotheticals, an attacker could then return a controlled void* memslot_validate_virt to callers, who would then start processing those chunks of memory leading to further decay.

That's my argument for potential (not easy, but possible) code execution. Highly likely I missed something, let me know. :)

Comment 6 Scott Gayou 2019-01-16 18:54:08 UTC
Also looks like an attacker may be able to change the group_id via modifying the QXL guest driver.

```c
void spiceqxl_display_monitors_config(qxl_screen_t *qxl)
{
    spice_qxl_monitors_config_async(&qxl->display_sin, physical_address(qxl, qxl->monitors_config, 0),
                                    MEMSLOT_GROUP, 0);
}
```
MEMSLOT_GROUP is hardcoded to 0. So I believe an attacker can control this? Untested.

Comment 7 Scott Gayou 2019-01-16 19:01:19 UTC
Original commit -- a70110c4e50aad99de7a844bb78eb868768e7841 on Sat Nov 21 22:42:38 2009 +0200

```bash
git tag --contains a70110c4e50aad99de7a844bb78eb868768e7841 | sort -V
0.5.2
0.5.3
0.6.0
0.6.1
0.6.2
0.6.3
0.6.4
0.7.0
0.7.1
0.7.2
0.7.3
0.8.0
0.8.1
0.8.2
0.8.3
0.9.0
0.9.1
0.10.0
0.10.1
0.11.0
0.11.3
0.12.0
v0.12.0
v0.12.2
v0.12.3
v0.12.4
v0.12.5
v0.12.6
v0.12.8
v0.13.0
v0.13.1
v0.13.2
v0.13.3
v0.13.90
v0.13.91
v0.14.0
v0.14.1
```

Thus, RHEL6 (spice-server 12.4) also impacted via the get_virt function in red_memslots.c.

Comment 8 Christophe Fergeau 2019-01-17 09:55:44 UTC
(In reply to Scott Gayou from comment #6)
> Also looks like an attacker may be able to change the group_id via modifying
> the QXL guest driver.
> 
> ```c
> void spiceqxl_display_monitors_config(qxl_screen_t *qxl)
> {
>     spice_qxl_monitors_config_async(&qxl->display_sin, physical_address(qxl,
> qxl->monitors_config, 0),
>                                     MEMSLOT_GROUP, 0);
> }
> ```
> MEMSLOT_GROUP is hardcoded to 0. So I believe an attacker can control this?
> Untested.

spiceqxl_*.c is not part of the QXL driver which runs on the guest, it's a xorg driver which will directly link with spice-server, and will allow to export a xorg display over SPICE. I'm fairly confident group_id is never obtained from guest data, but is always hardcoded to a valid value by QEMU.

Comment 9 Scott Gayou 2019-01-17 19:25:31 UTC
Acknowledgments:

Name: Christophe Fergeau (Red Hat)

Comment 16 Scott Gayou 2019-01-28 18:37:31 UTC
Created spice tracking bugs for this issue:

Affects: fedora-all [bug 1670158]

Comment 18 Scott Gayou 2019-01-28 18:38:40 UTC
Unembargoed.

Comment 19 errata-xmlrpc 2019-01-31 18:20:05 UTC
This issue has been addressed in the following products:

  Red Hat Enterprise Linux 7

Via RHSA-2019:0231 https://access.redhat.com/errata/RHSA-2019:0231

Comment 20 errata-xmlrpc 2019-01-31 18:20:24 UTC
This issue has been addressed in the following products:

  Red Hat Enterprise Linux 6

Via RHSA-2019:0232 https://access.redhat.com/errata/RHSA-2019:0232

Comment 26 errata-xmlrpc 2019-03-05 11:09:21 UTC
This issue has been addressed in the following products:

  Red Hat Virtualization 4 for Red Hat Enterprise Linux 7

Via RHSA-2019:0457 https://access.redhat.com/errata/RHSA-2019:0457