Skip to content

Use-After-Free Crash in plist_free() with Shared Node References #276

@LkkkLxy

Description

@LkkkLxy

Description of the bug

The libplist library has a critical use-after-free vulnerability when the same plist node is referenced from multiple locations within a tree structure. When plist_free() is called on such a structure, the recursive cleanup process frees the same node multiple times, leading to a crash in node_list_remove().

To Reproduce

Here's a minimal example that reproduces the use-after-free crash:

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

int main() {
    // Create dictionary
    plist_t dict = plist_new_dict();
    
    // Create a string node
    plist_t str_node = plist_new_string("shared_string");
    
    // Add the string node directly to dict
    plist_dict_set_item(dict, "direct", str_node);
    
    // Add the SAME node to an array, then add array to dict
    plist_t array = plist_new_array();
    plist_array_append_item(array, str_node);  // str_node referenced twice now
    plist_dict_set_item(dict, "in_array", array);
    
    // This triggers use-after-free because str_node is freed twice
    plist_free(dict);
    
    return 0;
}

Steps to reproduce:

  1. Compile the above code with libplist library and AddressSanitizer
  2. Run the program
  3. Observe the use-after-free crash in node_list_remove()

Compilation command:

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

Expected behavior

The library should handle shared node references correctly, either by:

  1. Implementing reference counting to track node usage
  2. Preventing creation of shared references at the API level
  3. Safely handling cleanup of shared nodes during plist_free()

Version Information

  • libplist version: Current master branch (tested on latest git commit)
  • OS and version: Linux Ubuntu 20.04
  • Compiler: Clang with AddressSanitizer
  • System: x86_64

Stack Trace

ERROR: AddressSanitizer: heap-use-after-free on address 0x504000000050 at pc 0x558e43808c73 bp 0x7ffffbf13dd0 sp 0x7ffffbf13dc8
READ of size 8 at 0x504000000050 thread T0
    #0 0x558e43808c72 in node_list_remove /src/libplist/libcnary/node_list.c:134
    #1 0x558e43807b96 in node_detach /src/libplist/libcnary/node.c:95:19
    #2 0x558e437fcb38 in plist_free_node /src/libplist/src/plist.c:419:22
    #3 0x558e437fcbb2 in plist_free_node /src/libplist/src/plist.c:427:9
    #4 0x558e437f391a in main (/src/test_reproduction+0x12391a)

0x504000000050 is located 0 bytes inside of 48-byte region [0x504000000050,0x504000000080)
freed by thread T0 here:
    #0 0x558e437b6166 in free /src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:52:3
    #1 0x558e437fcbd8 in plist_free_node /src/libplist/src/plist.c:431:5
    #2 0x558e437fcbb2 in plist_free_node /src/libplist/src/plist.c:427:9
    #3 0x558e437f391a in main (/src/test_reproduction+0x12391a)

SUMMARY: AddressSanitizer: heap-use-after-free /src/libplist/libcnary/node_list.c:134 in node_list_remove

Analysis

The crash occurs due to shared references in the plist tree structure:

  1. Shared node creation: The same str_node is added to both the dictionary directly and to an array within the dictionary
  2. Recursive cleanup issue: When plist_free(dict) is called, the recursive cleanup process encounters the same node multiple times
  3. Memory corruption: The node gets freed when encountered the first time, but is accessed again when the cleanup processes its second reference
  4. Crash location: The use-after-free manifests in node_list_remove() when trying to detach an already-freed node

Root causes:

  • Lack of reference counting mechanism for plist nodes
  • plist_free_node() assumes each node has only one parent
  • No detection of duplicate references during tree traversal

Security Impact

This vulnerability can be exploited for denial of service attacks by providing input that creates shared node references, causing applications to crash. Applications that process untrusted plist data and use the libplist API in ways that might create shared references are particularly at risk.

Affected APIs

The following APIs are involved in creating or managing the problematic structure:

  • plist_dict_set_item() - allows adding the same node to multiple containers
  • plist_array_append_item() - allows adding the same node to multiple containers
  • plist_free() - triggers the use-after-free during cleanup
  • plist_free_node() - recursive cleanup function with the actual flaw

Discussion

This bug was discovered through fuzz testing. I did not find any description in the documentation indicating that "shared references are prohibited," and I believe such behavior appears reasonable from a user’s perspective. I would be happy to discuss this issue with you further.

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