Skip to content

XML Entity Unescaping Length Inconsistency Causes Heap Buffer Overflow in libplist #277

@LkkkLxy

Description

@LkkkLxy

Description

A heap buffer overflow vulnerability exists in libplist when processing XML plist files containing entity escape sequences (such as <, >, &). The vulnerability is triggered by inconsistent length metadata after XML entity unescaping, leading to out-of-bounds memory access during OpenStep format conversion.

This bug was detected in fuzzing.

Root Cause

The vulnerability stems from a length metadata inconsistency between XML parsing and memory management:

  1. XML Parsing Phase: When parsing XML with entity escapes, text_parts_get_content calculates memory allocation based on pre-unescaping length but returns the smaller post-unescaping length
  2. Copy Phase: plist_copy_node preserves the original (incorrect) length field but reallocates string memory using strdup, which only allocates strlen() + 1 bytes based on the actual string content
  3. Access Phase: node_to_openstep uses the preserved length field to iterate, causing reads beyond the strdup-allocated memory

Technical Details

Crash Location: /src/libplist/src/oplist.c:203:47 in node_to_openstep function
Vulnerability Type: Heap buffer overflow (1-byte read beyond allocated memory)
Trigger Condition: XML plist with entity escapes + plist copy + OpenStep conversion

Call Stack:

#0 node_to_openstep (/src/libplist/src/oplist.c:203:47) - out-of-bounds read
#1 plist_to_openstep (/src/libplist/src/oplist.c:469:11)  
#2 Application code calling plist_to_openstep

Memory Allocation Info:

  • Allocated: 45 bytes via strdup in plist_copy_node (/src/libplist/src/plist.c:587:31)
  • Accessed: Attempt to read 46th byte, causing buffer overflow

Reproduction

Test Program

#include <plist/plist.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

int main() {
    // Hex data from crash report that contains entity-escaped XML
    unsigned char crash_data[] = {
        0x3c,0x3f,0x87,0x3f,0x3f,0x3e,0x3c,0x6b,0x65,0x79,0x0,0x0,0x0,0xff,0x3e,
        0xff,0xff,0xff,0xff,0xff,0xff,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
        0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
        0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x2,0x0,
        0x0,0x0,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
        0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
        0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0xff,0xff,0xff,0xff,0xff,
        0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
        0xff,0xff,0xff,0xff,0x3c,0x2f,0x6b,0x65,0x79,0x0,0xe7,0xe0,0x2f,0x3e,0x3c,0x64
    };
    
    printf("Testing XML entity unescaping length inconsistency bug...\\n");
    
    plist_t xml_plist = NULL;
    if (plist_from_xml((const char*)crash_data, sizeof(crash_data), &xml_plist) == PLIST_ERR_SUCCESS && xml_plist) {
        printf("XML parsed successfully\\n");
        
        // This creates the length inconsistency
        plist_t copied_plist = plist_copy(xml_plist);
        
        if (copied_plist) {
            printf("Plist copied successfully\\n");
            char* openstep = NULL;
            uint32_t openstep_len = 0;
            
            // This triggers the buffer overflow
            printf("Converting to OpenStep format...\\n");
            if (plist_to_openstep(copied_plist, &openstep, &openstep_len, 1) == PLIST_ERR_SUCCESS) {
                printf("Conversion successful\\n");
                if (openstep) free(openstep);
            }
            
            plist_free(copied_plist);
        }
        plist_free(xml_plist);
    }
    
    return 0;
}

Compilation

clang test.c -o test -fsanitize=address -I/usr/include/libplist-2.0 -lplist-2.0

Simplified Example

// Create a plist with entity-escaped XML content
const char* xml_with_entities = "<?xml version=\\"1.0\\"?><plist><key>Test &lt;String&gt;</key></plist>";
plist_t plist = NULL;
plist_from_xml(xml_with_entities, strlen(xml_with_entities), &plist);

// Copy creates the length inconsistency  
plist_t copied = plist_copy(plist);

// OpenStep conversion triggers overflow
char* result = NULL;
uint32_t len = 0;
plist_to_openstep(copied, &result, &len, 0);  // CRASH

AddressSanitizer Report

ERROR: AddressSanitizer: heap-buffer-overflow on address 0x5040000000bd at pc 0x556a6c6ea140 bp 0x7ffef41eacb0 sp 0x7ffef41eaca8
READ of size 1 at 0x5040000000bd thread T0
    #0 0x556a6c6ea13f in node_to_openstep /src/libplist/src/oplist.c:203:47
    #1 0x556a6c6e810b in plist_to_openstep /src/libplist/src/oplist.c:469:11

0x5040000000bd is located 0 bytes after 45-byte region [0x504000000090,0x5040000000bd)
allocated by thread T0 here:
    #0 0x556a6c6882da in strdup /src/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:570:3  
    #1 0x556a6c6efd26 in plist_copy_node /src/libplist/src/plist.c:587:31

SUMMARY: AddressSanitizer: heap-buffer-overflow /src/libplist/src/oplist.c:203:47 in node_to_openstep

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions