| Age | Commit message (Collapse) | Author | Files | Lines |
|
- check malloc/calloc/realloc results in constructors, the JSON and
OpenStep writers, the XML text-part helpers and base64 decoding, and
return PLIST_ERR_NO_MEM instead of asserting or dereferencing NULL
- guard size calculations against overflow in the UTF-8/UTF-16BE
conversions, ptrarray, and bytearray
- make hash_table_insert() report failure instead of silently dropping
the entry; writers propagate PLIST_ERR_NO_MEM, and dict lookup caches
are discarded so lookups fall back to a linear search
- don't overwrite an earlier parse error code in the XML parser, and
report PLIST_ERR_NO_MEM when reading text content fails for lack of memory
- OpenStep parser: check node/data/string allocations and dict/array
insertion results
Based on #323 by bg0d-droid.
Co-authored-by: bg0d-droid <81825610+bg0d-droid@users.noreply.github.com>
|
|
node_from_xml() hands a freshly created node over to the tree (as
root, or by adding it to a dict/array parent), but only reset `subnode`
at the very end of the block. If an error occurred in between, e.g. the
allocation of the node path item failing right after the root node was
assigned to *plist, err_out freed the node while the tree still
referenced it, and plist_free(*plist) then freed it a second time.
Clear `subnode` right after the node has been successfully attached so
that err_out can never free a node owned by the tree. Also report
PLIST_ERR_NO_MEM instead of PLIST_ERR_PARSE when allocating the node
path item fails.
Co-authored-by: bg0d-droid <81825610+bg0d-droid@users.noreply.github.com>
|
|
byte_array_grow() increased the capacity by a single page at a time, so
building large outputs (e.g. the plutil writer on a plist with heavily
shared references) caused a realloc and memcpy per page, i.e. quadratic
behavior that ran into fuzzer timeouts.
Double the capacity when growing. If the larger allocation fails, fall
back to the minimum size required before putting the array into its
failed state, so low-memory systems are not penalized by the extra
headroom. Also guard against size_t overflow in the capacity arithmetic.
|
|
Binary plist objects may be referenced multiple times, and each
reference is parsed into a separate node. A tiny crafted input where
arrays reference the same objects repeatedly (without forming a cycle,
so the recursion check does not trigger) expands into millions of
nodes and exhausts memory.
Track the number of nodes parsed and fail with PLIST_ERR_PARSE once it
exceeds num_objects * 16 + 65536. Legitimate files create roughly as
many nodes as they have objects, so they stay well below this limit.
Credit to OSSFuzz
|
|
IS_LEAP() computed `tm_year + 1900` in int arithmetic, which overflows
for dates around year 2^31 (hit via check_tm() when writing dates).
Cast to Year (64-bit) in IS_LEAP(). Also make gmtime64_r() return NULL
with EOVERFLOW when the calendar year (tm_year + 1900) does not fit in
an int; the previous check compared tm_year with itself and could never
fail. This avoids strftime() wrapping the year in the output, and
callers fall back to their existing default date string.
Credit to OSSFuzz
|
|
node_from_xml() ignored the return values of plist_dict_set_item() and
plist_array_append_item(). When the insertion failed (e.g. when the
nesting depth limit is hit in node_attach()), the newly created node was
never attached, yet subnode was reset to NULL, so the node, its data and
its string value were leaked.
Check the return values and bail out with PLIST_ERR_PARSE so that the
unattached node is freed in the error path.
Credit to OSSFuzz
|
|
plist_copy_node() kept a pointer `f` to the top frame of its traversal
stack and dereferenced it after the stack was grown with realloc(),
which may move the buffer. Plists nested deeper than 64 levels hit this
path, causing a heap-use-after-free when reading f->depth.
Read the child depth before the realloc and use it when building the
new frame.
Thanks to @sdnuhfiu for reporting!
Fixes #328
|
|
plist_dict_get_bool() compared the string value with strcmp() but treated a
non-zero return as a match. strcmp() returns 0 on equality, so the conditions
were inverted: "true" produced 0, "false" produced 1, and any other string
also produced 1. The error branch could never be reached, since it required
both comparisons to return 0 at once.
The result is that the API returns the opposite of the stored value for both
valid boolean strings, and returns true for strings that are not booleans at
all, instead of reporting the conversion error.
Compare == 0 in both conditions:
input before after
true 0 1
false 1 0
not-a-bool 1 error
TRUE 1 error
(empty) 1 error
Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
|
|
byte_array_grow() assigned the result of realloc() directly to
ba->data and increased the capacity even if realloc() failed, so the
following memcpy() in byte_array_append() wrote to NULL + len.
On failure, free the old buffer and leave the byte array in a failed
state (data == NULL), which byte_array_append() already ignores. The
callers check for that state:
- the writers (bin, xml, json, openstep and the text output formats)
return PLIST_ERR_NO_MEM instead of handing out a NULL/truncated buffer
- node_to_xml() base64-encodes <data> directly into the grown buffer,
so it must bail out there (only guarded by assert() before)
- the OpenStep parser returns PLIST_ERR_NO_MEM for <hex data> instead of
silently returning truncated data
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
ptr_array_insert() assigned the result of realloc() directly to
pa->pdata and bumped the capacity without checking for failure, so on
out-of-memory the old buffer was leaked and the following store or
memmove() wrote through a NULL pointer.
Keep the old buffer on failure and return -1 from ptr_array_insert()
and ptr_array_add(). The callers now handle the error:
- the array lookup cache (plist.c) is dropped instead of silently going
out of sync with the node list; lookups then fall back to walking
the children, as they do before the cache exists
- bplist parsing (used_indexes) and serialization (objects) return
PLIST_ERR_NO_MEM; ignoring the error in parse_bin_node_at_index()
would otherwise loop forever since the array never grows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
|
|
|
|
Avoid undefined behavior when serializing malformed PLIST_DATE values
containing NaN, infinity, or values outside the Time64_T range. Add a
shared helper for checked date conversion and use it across writer paths.
|
|
|
|
Improve robustness and memory safety in node_to_string() and dtostr():
- add missing NULL and allocation checks
- fix snprintf() error handling and signed/unsigned conversions
- replace sprintf() with snprintf()
- fix base64 buffer sizing
- switch string offset tracking to size_t
- improve malformed data handling for strings and data blobs
|
|
|
|
Credit to @Bri1987
|
|
Convert all array/dict modification functions from void to plist_err_t return type:
- plist_array_set_item: replace at index n
- plist_array_append_item: append to end
- plist_array_insert_item: insert at position n
- plist_array_remove_item: remove item at index n
- plist_array_item_remove: remove item from its array parent
- plist_dict_set_item: replace or insert key/value
- plist_dict_remove_item: remove key/value pair
- plist_dict_merge: merge source dict into target
Returns:
- PLIST_ERR_SUCCESS on success
- PLIST_ERR_INVALID_ARG for invalid arguments (NULL, wrong type, out of range, etc.)
- PLIST_ERR_NO_MEM on memory allocation failure
- PLIST_ERR_UNKNOWN on unexpected internal errors
Header documentation updated with full error code semantics for each function.
|
|
- Use PLIST_OPT_COERCE option to coerce PLIST_BOOLEAN, PLIST_DATE, PLIST_UID, and PLIST_NULL to OpenStep-compatible types (1 or 0, ISO 8601 strings, integers, and 'NULL' string)
- Add plist_to_openstep_with_options() function to allow passing coercion option (and others)
- Update plist_write_to_string() and plist_write_to_stream() accordingly
|
|
- Add PLIST_OPT_COERCE option to coerce PLIST_DATE, PLIST_DATA, and PLIST_UID to JSON-compatible types (ISO 8601 strings, Base64 strings, and integers)
- Add plist_to_json_with_options() function to allow passing coercion options (and others)
- Update plist_write_to_string() and plist_write_to_stream() to support coercion option
- Add --coerce flag to plistutil for JSON output
- Create plist2json symlink that automatically enables coercion when invoked
|
|
Ensure that XML property lists contain exactly one root value inside the <plist> element and reject any additional value nodes before </plist>.
Add tests covering root value handling and nested CF$UID conversion behavior.
Co-authored-by: Sami Kortelainen <sami.kortelainen@piceasoft.com>
Co-authored-by: Nikias Bassen <nikias@gmx.li>
|
|
Convert single-entry { "CF$UID" : <integer> } dictionaries to PLIST_UID
nodes when closing a dict in the XML parser.
Refactor node cleanup logic:
- Split plist_free_data() into internal _plist_free_data()
- Introduce plist_free_children() to release child nodes separately
- Update plist_set_element_val() to free children before changing
container node types
- Ensure PLIST_DICT hashtables do not free values (assert + force
free_func = NULL)
This avoids in-place container mutation issues and ensures child
nodes and container metadata are released correctly before
changing node type.
Co-authored-by: Sami Kortelainen <sami.kortelainen@piceasoft.com>
Co-authored-by: Nikias Bassen <nikias@gmx.li>
|
|
|
|
Credit to OSSFuzz
|
|
|
|
Introduce private iterator structs for plist_array_iter and
plist_dict_iter, and fix *_next_item() to properly advance
iterator state and handle malformed containers safely.
|
|
|
|
Convert plist_free_node() and plist_copy_node() to iterative
implementations. This avoids unbounded recursion and stack
overflow when handling deeply nested plist data, while
preserving existing semantics and caches.
|
|
Update plist array and dict mutation helpers to check
return values from node_attach() and node_insert(). This
prevents cache corruption and allows new depth and cycle
checks to be enforced correctly.
|
|
This change adds stricter validation for numeric and date nodes,
including full-input consumption, overflow/range checks, and rejection
of invalid floating-point values. Whitespace handling is clarified by
explicitly trimming trailing XML whitespace for value nodes.
|
|
This removes the necessity for malloc failures and reduces overhead
|
|
Use explicit key/value stepping, zero-initialize hash lookup key,
and perform length-checked comparisons on NUL-terminated key strings.
|
|
Credit to @ylwango613
|
|
Signed-off-by: Rosen Penev <rosenp@gmail.com>
|
|
Fixes issue #300
Credit to @jasonmli8
|
|
|
|
- Fix numeric character reference parsing
- Enforce exact entity name matching
- Guard against size_t underflow and oversized entities
- Reject invalid Unicode code points
|
|
Fixes #285
Credit to @ylwango613 for reporting.
|
|
- Treat input as unsigned bytes
- Correct UTF-8 bit decoding for 2/3/4-byte sequences
- Add overlong, surrogate, and range checks
- Enforce lead/continuation byte constraints
This addresses issue #283.
Credit to @hgarrereyn for reporting.
|
|
|
|
Use size_t for token start/end offsets instead of int, replace the -1
sentinel with SIZE_MAX, and add a defensive guard against offset
wraparound. This prevents overflow when parsing very large JSON inputs.
This addresses issue #282.
Credit to @ylwango613 for repporting.
|
|
Credit to @LkkkLxy. Addresses #277.
|
|
Credit to @LkkkLxy for reporting (#276).
libplist nodes are owned by exactly one container. Inserting the same
plist_t into multiple dicts or arrays corrupts the tree structure and
leads to use-after-free crashes during traversal or plist_free().
Add explicit parent checks to dict and array insertion APIs to reject
nodes that already belong to another container. In debug builds, this
fails loudly via assert() and optional diagnostics; in release builds,
the operation safely no-ops.
Callers that need to reuse values must create a copy using plist_copy()
or explicitly detach the node before reinserting it.
|
|
|
|
Thanks to @unbengable12 for reporting. Addresses #288, #289, #290, #291, and #292.
|
|
|
|
Thanks to @LkkkLxy for pointing out the issue.
|
|
member
|
|
Ensure plist_data_compare safely handles NULL inputs by normalizing
NULL data to empty values and avoiding invalid dereferences.
|
|
Credit to OSS-Fuzz
|