-
-
Notifications
You must be signed in to change notification settings - Fork 328
Description
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:
- Compile the above code with libplist library and AddressSanitizer
- Run the program
- 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:
- Implementing reference counting to track node usage
- Preventing creation of shared references at the API level
- 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:
- Shared node creation: The same
str_node
is added to both the dictionary directly and to an array within the dictionary - Recursive cleanup issue: When
plist_free(dict)
is called, the recursive cleanup process encounters the same node multiple times - Memory corruption: The node gets freed when encountered the first time, but is accessed again when the cleanup processes its second reference
- 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 containersplist_array_append_item()
- allows adding the same node to multiple containersplist_free()
- triggers the use-after-free during cleanupplist_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.