summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Nathan Chandrasekar2026-10-17 20:14:17 -0400
committerGravatar Nikias Bassen2026-11-05 23:46:59 +0100
commit70a64c23c9d77b114dcc71e70e69aa8868693f3f (patch)
treea2241d7e9d25cdedbb01cc9c4dbfd766ec6bbce8
parent86def304f2915a13170bd0ff7adf0816ab3178a6 (diff)
downloadlibplist-70a64c23c9d77b114dcc71e70e69aa8868693f3f.tar.gz
libplist-70a64c23c9d77b114dcc71e70e69aa8868693f3f.tar.bz2
test: Add binary plist parser behavior tests
Build small binary plists in memory and check that arrays and dicts parse correctly with 1, 2 and 3 byte object references, and that strings, extended size markers and array/dict references extending beyond the object data area into the offset table are rejected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--.gitignore1
-rw-r--r--test/Makefile.am7
-rwxr-xr-xtest/bplist_behavior.test2
-rw-r--r--test/bplist_behavior_test.c199
4 files changed, 208 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index a59b057..d7d27da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,6 +55,7 @@ test/plist_jtest
test/plist_otest
test/integer_set_test
test/xml_behavior_test
+test/bplist_behavior_test
test/data/*.out
test/*.trs
cython/Makefile
diff --git a/test/Makefile.am b/test/Makefile.am
index 76d2373..1d98349 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -14,7 +14,8 @@ noinst_PROGRAMS = \
plist_btest \
plist_jtest \
plist_otest \
- xml_behavior_test
+ xml_behavior_test \
+ bplist_behavior_test
plist_cmp_SOURCES = plist_cmp.c
plist_cmp_LDADD = \
@@ -42,6 +43,9 @@ plist_otest_LDADD = $(top_builddir)/src/libplist-2.0.la
xml_behavior_test_SOURCES = xml_behavior_test.c
xml_behavior_test_LDADD = $(top_builddir)/src/libplist-2.0.la
+bplist_behavior_test_SOURCES = bplist_behavior_test.c
+bplist_behavior_test_LDADD = $(top_builddir)/src/libplist-2.0.la
+
TESTS = \
empty.test \
small.test \
@@ -85,6 +89,7 @@ TESTS = \
ostep-comments.test \
ostep-invalid-types.test \
xml_behavior.test \
+ bplist_behavior.test \
plistutil-streams.test
EXTRA_DIST = \
diff --git a/test/bplist_behavior.test b/test/bplist_behavior.test
new file mode 100755
index 0000000..130a6a0
--- /dev/null
+++ b/test/bplist_behavior.test
@@ -0,0 +1,2 @@
+## -*- sh -*-
+$top_builddir/test/bplist_behavior_test
diff --git a/test/bplist_behavior_test.c b/test/bplist_behavior_test.c
new file mode 100644
index 0000000..f7365f1
--- /dev/null
+++ b/test/bplist_behavior_test.c
@@ -0,0 +1,199 @@
+/*
+ * bplist_behavior_test.c
+ *
+ * Tests binary plist parser behavior around the object data area:
+ *
+ * 1) Arrays and dictionaries are parsed correctly for different
+ * object reference sizes, including sizes that are not a power of 2.
+ *
+ * 2) Objects whose data extends beyond the object data area, i.e.
+ * into the offset table, must cause parsing to fail. This covers
+ * strings, extended size markers, array references and dictionary
+ * key/value references.
+ *
+ * The binary plists are assembled in memory with a minimal writer so
+ * each test case only has to specify the object bytes.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include "plist/plist.h"
+
+#define MAX_BPLIST_SIZE 256
+
+static void put_be64(uint8_t *p, uint64_t v)
+{
+ int i;
+ for (i = 7; i >= 0; i--) {
+ p[i] = (uint8_t)(v & 0xFF);
+ v >>= 8;
+ }
+}
+
+/*
+ * Assemble a binary plist from the given object bytes. offsets[] holds the
+ * start of each object relative to the beginning of objs. A 1 byte offset
+ * table is placed directly after the objects, followed by the trailer.
+ */
+static uint32_t build_bplist(uint8_t *out, const uint8_t *objs, size_t objs_len, const uint8_t *offsets, size_t num_objects, uint8_t ref_size, uint64_t root)
+{
+ size_t len = 0;
+ size_t i;
+ memcpy(out, "bplist00", 8);
+ len = 8;
+ memcpy(out + len, objs, objs_len);
+ len += objs_len;
+ size_t offset_table_offset = len;
+ for (i = 0; i < num_objects; i++) {
+ out[len++] = (uint8_t)(8 + offsets[i]);
+ }
+ memset(out + len, 0, 6);
+ out[len + 6] = 1; /* offset size */
+ out[len + 7] = ref_size;
+ put_be64(out + len + 8, num_objects);
+ put_be64(out + len + 16, root);
+ put_be64(out + len + 24, offset_table_offset);
+ len += 32;
+ return (uint32_t)len;
+}
+
+static int check_int_item(plist_t item, uint64_t expected, const char *name)
+{
+ uint64_t val = 0;
+ if (plist_get_node_type(item) != PLIST_INT) {
+ fprintf(stderr, "%s: expected PLIST_INT item\n", name);
+ return 0;
+ }
+ plist_get_uint_val(item, &val);
+ if (val != expected) {
+ fprintf(stderr, "%s: expected %" PRIu64 ", got %" PRIu64 "\n", name, expected, val);
+ return 0;
+ }
+ return 1;
+}
+
+static int test_array_ref_sizes(void)
+{
+ /* [ 1, 2 ] with 1 byte object references */
+ const uint8_t objs1[] = { 0xA2, 0x01, 0x02, 0x10, 0x01, 0x10, 0x02 };
+ const uint8_t offs1[] = { 0, 3, 5 };
+ /* [ 1, 2 ] with 3 byte object references */
+ const uint8_t objs3[] = { 0xA2, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x10, 0x01, 0x10, 0x02 };
+ const uint8_t offs3[] = { 0, 7, 9 };
+ struct {
+ const uint8_t *objs;
+ size_t objs_len;
+ const uint8_t *offs;
+ uint8_t ref_size;
+ } cases[] = {
+ { objs1, sizeof(objs1), offs1, 1 },
+ { objs3, sizeof(objs3), offs3, 3 },
+ };
+ size_t i;
+
+ for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
+ uint8_t buf[MAX_BPLIST_SIZE];
+ uint32_t len = build_bplist(buf, cases[i].objs, cases[i].objs_len, cases[i].offs, 3, cases[i].ref_size, 0);
+ plist_t root = NULL;
+ plist_err_t err = plist_from_bin((const char*)buf, len, &root);
+ if (err != PLIST_ERR_SUCCESS || !root) {
+ fprintf(stderr, "array ref_size %u: plist_from_bin failed (err=%d)\n", cases[i].ref_size, err);
+ plist_free(root);
+ return 0;
+ }
+ if (plist_get_node_type(root) != PLIST_ARRAY || plist_array_get_size(root) != 2) {
+ fprintf(stderr, "array ref_size %u: expected array with 2 items\n", cases[i].ref_size);
+ plist_free(root);
+ return 0;
+ }
+ if (!check_int_item(plist_array_get_item(root, 0), 1, "array item 0") ||
+ !check_int_item(plist_array_get_item(root, 1), 2, "array item 1")) {
+ plist_free(root);
+ return 0;
+ }
+ plist_free(root);
+ }
+ return 1;
+}
+
+static int test_dict_ref_size(void)
+{
+ /* { "a": 5 } with 2 byte object references */
+ const uint8_t objs[] = { 0xD1, 0x00, 0x01, 0x00, 0x02, 0x51, 'a', 0x10, 0x05 };
+ const uint8_t offs[] = { 0, 5, 7 };
+ uint8_t buf[MAX_BPLIST_SIZE];
+ uint32_t len = build_bplist(buf, objs, sizeof(objs), offs, 3, 2, 0);
+ plist_t root = NULL;
+ plist_err_t err = plist_from_bin((const char*)buf, len, &root);
+ if (err != PLIST_ERR_SUCCESS || !root) {
+ fprintf(stderr, "dict ref_size 2: plist_from_bin failed (err=%d)\n", err);
+ plist_free(root);
+ return 0;
+ }
+ if (plist_get_node_type(root) != PLIST_DICT || plist_dict_get_size(root) != 1) {
+ fprintf(stderr, "dict ref_size 2: expected dict with 1 entry\n");
+ plist_free(root);
+ return 0;
+ }
+ if (!check_int_item(plist_dict_get_item(root, "a"), 5, "dict value")) {
+ plist_free(root);
+ return 0;
+ }
+ plist_free(root);
+ return 1;
+}
+
+static int expect_rejected(const uint8_t *objs, size_t objs_len, const uint8_t *offs, size_t num_objects, uint64_t root_index, const char *name)
+{
+ uint8_t buf[MAX_BPLIST_SIZE];
+ uint32_t len = build_bplist(buf, objs, objs_len, offs, num_objects, 1, root_index);
+ plist_t root = NULL;
+ plist_err_t err = plist_from_bin((const char*)buf, len, &root);
+ if (err == PLIST_ERR_SUCCESS || root != NULL) {
+ fprintf(stderr, "%s: expected failure, got err=%d root=%p\n", name, err, (void*)root);
+ plist_free(root);
+ return 0;
+ }
+ return 1;
+}
+
+static int test_data_beyond_object_area_is_rejected(void)
+{
+ int ok = 1;
+
+ /* string claims 5 bytes, only 2 are present before the offset table */
+ const uint8_t str_objs[] = { 0x55, 'a', 'b' };
+ const uint8_t str_offs[] = { 0 };
+ ok &= expect_rejected(str_objs, sizeof(str_objs), str_offs, 1, 0, "truncated string");
+
+ /* extended size marker, but no size integer before the offset table */
+ const uint8_t ext_objs[] = { 0x5F };
+ const uint8_t ext_offs[] = { 0 };
+ ok &= expect_rejected(ext_objs, sizeof(ext_objs), ext_offs, 1, 0, "missing size integer");
+
+ /* array claims 3 references, only 2 are present before the offset table */
+ const uint8_t arr_objs[] = { 0x10, 0x01, 0x10, 0x02, 0xA3, 0x00, 0x01 };
+ const uint8_t arr_offs[] = { 0, 2, 4 };
+ ok &= expect_rejected(arr_objs, sizeof(arr_objs), arr_offs, 3, 2, "truncated array references");
+
+ /* dict has its key reference, but the value reference is missing */
+ const uint8_t dict_objs[] = { 0x51, 'a', 0x10, 0x05, 0xD1, 0x00 };
+ const uint8_t dict_offs[] = { 0, 2, 4 };
+ ok &= expect_rejected(dict_objs, sizeof(dict_objs), dict_offs, 3, 2, "truncated dict references");
+
+ return ok;
+}
+
+int main(void)
+{
+ int ok = 1;
+
+ ok &= test_array_ref_sizes();
+ ok &= test_dict_ref_size();
+ ok &= test_data_beyond_object_area_is_rejected();
+
+ return ok ? 0 : 1;
+}