<feed xmlns='http://www.w3.org/2005/Atom'>
<title>libplist/src, branch 2.8.1</title>
<subtitle>Library to handle Apple Property List format files in binary or XML</subtitle>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/'/>
<entry>
<title>plist: treat NaN date values as 0 in plist_get_date_val</title>
<updated>2026-11-06T09:32:42+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-11-06T09:32:42+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=a3215e2a6577df39d944578af671f9dae2770336'/>
<id>a3215e2a6577df39d944578af671f9dae2770336</id>
<content type='text'>
Converting NaN to int32_t is undefined behavior, and NaN can come from
a parsed date node.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Converting NaN to int32_t is undefined behavior, and NaN can come from
a parsed date node.
</pre>
</div>
</content>
</entry>
<entry>
<title>plist: fix the (sec, usec) date API round trip</title>
<updated>2026-11-06T09:29:06+00:00</updated>
<author>
<name>Flavio Toffalini</name>
</author>
<published>2026-10-26T09:03:57+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=1d3b54c784da260f71d885f5f560d69c211a645e'/>
<id>1d3b54c784da260f71d885f5f560d69c211a645e</id>
<content type='text'>
plist_set_date_val stores val = sec + usec / 1000000, but
plist_get_date_val splits it with a truncating cast and fabs():

    *sec  = (int32_t)val;
    val   = fabs((val - (int64_t)val) * 1000000);
    *usec = (int32_t)val;

Two results are wrong. The product is truncated, so set(5, 6) reads
back as (5, 5) - 29.3% of round trips over sec in [0, 3] come back a
microsecond short. And for a negative value, truncation toward zero
plus fabs() give a pair that does not denote the same instant:
set(-3, 250000) stores -2.75 and reads back as (-2, 750000), which the
setter would store as -1.25. Every round trip with sec &lt; 0 and
usec != 0 is wrong.

(int32_t)val is also undefined when the stored value is outside the
int32_t range.

Round the seconds down, round the fraction to microseconds carrying
1000000 into the seconds, and clamp the seconds before converting, so
sec + usec / 1000000 == val to the microsecond with 0 &lt;= usec &lt;
1000000 - the convention the setter uses.

Add test/date_val.c covering fixed cases and a sweep over sec in
[-2, 2]. It fails on an unmodified tree - set(5, 6), set(-3, 250000)
and set(-1, 1) all come back as different instants - and passes with
this change, with make check at 44/44.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Arn4YaWtcQBDsQuJtyBmFP
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
plist_set_date_val stores val = sec + usec / 1000000, but
plist_get_date_val splits it with a truncating cast and fabs():

    *sec  = (int32_t)val;
    val   = fabs((val - (int64_t)val) * 1000000);
    *usec = (int32_t)val;

Two results are wrong. The product is truncated, so set(5, 6) reads
back as (5, 5) - 29.3% of round trips over sec in [0, 3] come back a
microsecond short. And for a negative value, truncation toward zero
plus fabs() give a pair that does not denote the same instant:
set(-3, 250000) stores -2.75 and reads back as (-2, 750000), which the
setter would store as -1.25. Every round trip with sec &lt; 0 and
usec != 0 is wrong.

(int32_t)val is also undefined when the stored value is outside the
int32_t range.

Round the seconds down, round the fraction to microseconds carrying
1000000 into the seconds, and clamp the seconds before converting, so
sec + usec / 1000000 == val to the microsecond with 0 &lt;= usec &lt;
1000000 - the convention the setter uses.

Add test/date_val.c covering fixed cases and a sweep over sec in
[-2, 2]. It fails on an unmodified tree - set(5, 6), set(-3, 250000)
and set(-1, 1) all come back as different instants - and passes with
this change, with make check at 44/44.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Arn4YaWtcQBDsQuJtyBmFP
</pre>
</div>
</content>
</entry>
<entry>
<title>bplist: Guard uint64_mul_overflow fallback against division by zero</title>
<updated>2026-11-06T08:39:42+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-11-06T08:39:42+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=9fdbf585f47501ba19a53fd4bb382d5b533e3fdd'/>
<id>9fdbf585f47501ba19a53fd4bb382d5b533e3fdd</id>
<content type='text'>
The portable fallback used when __builtin_umulll_overflow is not
available computed a &gt; UINT64_MAX / b, which divides by zero if b is 0.
No current caller passes 0, but return no overflow in that case so the
helper is safe for any input.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The portable fallback used when __builtin_umulll_overflow is not
available computed a &gt; UINT64_MAX / b, which divides by zero if b is 0.
No current caller passes 0, but return no overflow in that case so the
helper is safe for any input.
</pre>
</div>
</content>
</entry>
<entry>
<title>bplist: Use length-based range checks for object data</title>
<updated>2026-11-05T22:46:59+00:00</updated>
<author>
<name>Nathan Chandrasekar</name>
</author>
<published>2026-10-18T00:14:17+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=86def304f2915a13170bd0ff7adf0816ab3178a6'/>
<id>86def304f2915a13170bd0ff7adf0816ab3178a6</id>
<content type='text'>
The binary plist parser validated object data by computing pointers such
as `object + size` or `refs + j * ref_size` and comparing them against the
offset table. Each of these checks had to be written with its own
wrap-around handling, and the pointer was formed before it was known to
be inside the input buffer.

Add bplist_object_range_valid(), which checks a length against the bytes
remaining before the offset table, and bplist_read_object_ref(), which
reads an array/dict object reference with an overflow-checked offset.
Use them for all object data checks in parse_bin_node() and for the
reference reads in parse_array_node() and parse_dict_node(). Arrays and
dicts now validate the full reference list (count * ref_size, or twice
that for dicts) up front instead of only the item count.

The node offset checks in parse_bin_node_at_index() and the offset table
check in plist_from_bin() compare offsets instead of pointers as well.

Valid binary plists are parsed exactly as before.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The binary plist parser validated object data by computing pointers such
as `object + size` or `refs + j * ref_size` and comparing them against the
offset table. Each of these checks had to be written with its own
wrap-around handling, and the pointer was formed before it was known to
be inside the input buffer.

Add bplist_object_range_valid(), which checks a length against the bytes
remaining before the offset table, and bplist_read_object_ref(), which
reads an array/dict object reference with an overflow-checked offset.
Use them for all object data checks in parse_bin_node() and for the
reference reads in parse_array_node() and parse_dict_node(). Arrays and
dicts now validate the full reference list (count * ref_size, or twice
that for dicts) up front instead of only the item count.

The node offset checks in parse_bin_node_at_index() and the offset table
check in plist_from_bin() compare offsets instead of pointers as well.

Valid binary plists are parsed exactly as before.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>oplist: detect errors when parsing 'strings' data</title>
<updated>2026-11-05T11:02:11+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-11-05T11:02:11+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=9d238e5e1a55a126c8d19d644a5a2a9c305eb564'/>
<id>9d238e5e1a55a126c8d19d644a5a2a9c305eb564</id>
<content type='text'>
plist_from_openstep() tested ctx.err &gt; 0 after parsing the plist as
'strings' data, but error codes are negative, so a failure was never
detected and the function returned success with a NULL plist (leaking the
temporary dict). Check for any error and return it.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
plist_from_openstep() tested ctx.err &gt; 0 after parsing the plist as
'strings' data, but error codes are negative, so a failure was never
detected and the function returned success with a NULL plist (leaking the
temporary dict). Check for any error and return it.
</pre>
</div>
</content>
</entry>
<entry>
<title>Fix leaks and crashes on allocation failure</title>
<updated>2026-11-05T11:02:05+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-11-05T11:02:05+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=9fd3406baec5997d386cdb4c7492117604ba0a70'/>
<id>9fd3406baec5997d386cdb4c7492117604ba0a70</id>
<content type='text'>
Found by failing each allocation in turn under ASan/UBSan/LSan:

- plist_free() leaked the whole tree if its work stack could not be
  allocated; free the children iteratively without extra memory
- plist_copy(): when a shallow copy of a dict/array failed, the original's
  lookup table was freed along with the copy's data (use-after-free)
- bplist parser: fix a NULL dereference in parse_date_node(), check the
  node_attach() results, and don't leak on failure; plist_to_bin() no
  longer leaks on an allocation failure
- free the plist data when node creation fails (plist constructors, XML,
  JSON and binary parsers)
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Found by failing each allocation in turn under ASan/UBSan/LSan:

- plist_free() leaked the whole tree if its work stack could not be
  allocated; free the children iteratively without extra memory
- plist_copy(): when a shallow copy of a dict/array failed, the original's
  lookup table was freed along with the copy's data (use-after-free)
- bplist parser: fix a NULL dereference in parse_date_node(), check the
  node_attach() results, and don't leak on failure; plist_to_bin() no
  longer leaks on an allocation failure
- free the plist data when node creation fails (plist constructors, XML,
  JSON and binary parsers)
</pre>
</div>
</content>
</entry>
<entry>
<title>Harden allocation failure handling and size arithmetic</title>
<updated>2026-11-05T11:01:59+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-11-05T11:01:59+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=4dc93bc274867b42aa82fc91ee4d5878d23a3f36'/>
<id>4dc93bc274867b42aa82fc91ee4d5878d23a3f36</id>
<content type='text'>
- 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 &lt;81825610+bg0d-droid@users.noreply.github.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
- 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 &lt;81825610+bg0d-droid@users.noreply.github.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>xplist: fix double-free in node_from_xml() error paths</title>
<updated>2026-11-05T10:59:00+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-11-05T09:44:41+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=f252f243f9774b2b830172b49aa340d6994d8081'/>
<id>f252f243f9774b2b830172b49aa340d6994d8081</id>
<content type='text'>
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 &lt;81825610+bg0d-droid@users.noreply.github.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
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 &lt;81825610+bg0d-droid@users.noreply.github.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>bytearray: grow buffer geometrically to avoid quadratic appends</title>
<updated>2026-11-05T09:40:10+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-11-05T09:40:10+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=40666fa02457c84c3b8561b2bb36928bd23e9f04'/>
<id>40666fa02457c84c3b8561b2bb36928bd23e9f04</id>
<content type='text'>
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.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
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.
</pre>
</div>
</content>
</entry>
<entry>
<title>bplist: limit total parsed nodes to prevent memory exhaustion</title>
<updated>2026-11-05T09:07:22+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-11-05T09:07:22+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=2b3cc554270d34ca1e1fae3eb2cbbc740a65bbf3'/>
<id>2b3cc554270d34ca1e1fae3eb2cbbc740a65bbf3</id>
<content type='text'>
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
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
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
</pre>
</div>
</content>
</entry>
</feed>
