Fix multiple CVEs in glib and glib-utils.
Introduces patches:
1. https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3126 Backport !3125
“Various fixes to normal form handling in GVariant” to glib-2-74
To fix CVE-2023-29499, CVE-2023-32665, CVE-2023-32611.
The set of first patches introduces more vulnerabilities which are fixed in
2. https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3164 Backport !3163
“gvariant: Check offset table doesn’t fall outside variant bounds and speed
up text parsing” to glib-2-74
To fix CVE-2023-32636, CVE-2023-32643
BUG=b/301208987, b/301383832, b/301208986, b/301219422, b/301219451, b/301394701, b/301219743, b/301394621, b/301208945, b/301382032, b/301219919, b/301394702, b/301214643, b/301151645, b/301152946, b/301318568, b/301152847, b/301321855, b/301220192, b/301397146, b/301220055, b/301393170, b/301217297, b/301206803, b/301203614, b/301379067, b/301207397, b/301383831, b/301216401, b/301394620, b/301152837, b/301320043, b/301152761, b/301172478, b/301172933, b/301347449, b/301171741, b/301349120, b/301152754, b/301322359, b/301150464, b/301319883, b/301151036, b/301152815, b/301320399, b/301244156, b/301427580, b/301244157, b/301245412, b/301427579
TEST=presubmit
RELEASE_NOTE=Fix CVE-2023-32636, CVE-2023-29499, CVE-2023-32643, CVE-2023-32665,
CVE-2023-32611 in glib and glib-utils.
cos-patch: security-low
Change-Id: Id521c349937eac9308689a6982c984f31f9074d7
Reviewed-on: https://cos-review.googlesource.com/c/third_party/overlays/chromiumos-overlay/+/57694
Reviewed-by: Oleksandr Tymoshenko <ovt@google.com>
Main-Branch-Verified: Cusky Presubmit Bot <presubmit@cos-infra-prod.iam.gserviceaccount.com>
Tested-by: Cusky Presubmit Bot <presubmit@cos-infra-prod.iam.gserviceaccount.com>
diff --git a/dev-libs/glib/files/glib-2.74.1-normal-form-handling-gvariant-patch-fixes.patch b/dev-libs/glib/files/glib-2.74.1-normal-form-handling-gvariant-patch-fixes.patch
new file mode 100644
index 0000000..612d2ab
--- /dev/null
+++ b/dev-libs/glib/files/glib-2.74.1-normal-form-handling-gvariant-patch-fixes.patch
@@ -0,0 +1,200 @@
+From bf008669f26f70426ca30ee8cdcbd48e41a74718 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Thu, 15 Dec 2022 13:00:39 +0000
+Subject: [PATCH 1/2] =?UTF-8?q?gvariant:=20Check=20offset=20table=20doesn?=
+ =?UTF-8?q?=E2=80=99t=20fall=20outside=20variant=20bounds?=
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+When dereferencing the first entry in the offset table for a tuple,
+check that it doesn’t fall outside the bounds of the variant first.
+
+This prevents an out-of-bounds read from some non-normal tuples.
+
+This bug was introduced in commit 73d0aa81c2575a5c9ae77d.
+
+Includes a unit test, although the test will likely only catch the
+original bug if run with asan enabled.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Fixes: #2840
+oss-fuzz#54302
+---
+ glib/gvariant-serialiser.c | 12 ++++++--
+ glib/tests/gvariant.c | 63 ++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 72 insertions(+), 3 deletions(-)
+
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index e9b0eab2b4..af863cc526 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -984,7 +984,8 @@ gvs_tuple_get_member_bounds (GVariantSerialised value,
+
+ member_info = g_variant_type_info_member_info (value.type_info, index_);
+
+- if (member_info->i + 1)
++ if (member_info->i + 1 &&
++ offset_size * (member_info->i + 1) <= value.size)
+ member_start = gvs_read_unaligned_le (value.data + value.size -
+ offset_size * (member_info->i + 1),
+ offset_size);
+@@ -995,7 +996,8 @@ gvs_tuple_get_member_bounds (GVariantSerialised value,
+ member_start &= member_info->b;
+ member_start |= member_info->c;
+
+- if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_LAST)
++ if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_LAST &&
++ offset_size * (member_info->i + 1) <= value.size)
+ member_end = value.size - offset_size * (member_info->i + 1);
+
+ else if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_FIXED)
+@@ -1006,11 +1008,15 @@ gvs_tuple_get_member_bounds (GVariantSerialised value,
+ member_end = member_start + fixed_size;
+ }
+
+- else /* G_VARIANT_MEMBER_ENDING_OFFSET */
++ else if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_OFFSET &&
++ offset_size * (member_info->i + 2) <= value.size)
+ member_end = gvs_read_unaligned_le (value.data + value.size -
+ offset_size * (member_info->i + 2),
+ offset_size);
+
++ else /* invalid */
++ member_end = G_MAXSIZE;
++
+ if (out_member_start != NULL)
+ *out_member_start = member_start;
+ if (out_member_end != NULL)
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index ba837af48d..e4a2518aa6 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -5562,6 +5562,67 @@ test_normal_checking_tuple_offsets4 (void)
+ g_variant_unref (variant);
+ }
+
++/* This is a regression test that dereferencing the first element in the offset
++ * table doesn’t dereference memory before the start of the GVariant. The first
++ * element in the offset table gives the offset of the final member in the
++ * tuple (the offset table is stored in reverse), and the position of this final
++ * member is needed to check that none of the tuple members overlap with the
++ * offset table
++ *
++ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2840 */
++static void
++test_normal_checking_tuple_offsets5 (void)
++{
++ /* A tuple of type (sss) in normal form would have an offset table with two
++ * entries:
++ * - The first entry (lowest index in the table) gives the offset of the
++ * third `s` in the tuple, as the offset table is reversed compared to the
++ * tuple members.
++ * - The second entry (highest index in the table) gives the offset of the
++ * second `s` in the tuple.
++ * - The offset of the first `s` in the tuple is always 0.
++ *
++ * See §2.5.4 (Structures) of the GVariant specification for details, noting
++ * that the table is only layed out this way because all three members of the
++ * tuple have non-fixed sizes.
++ *
++ * It’s not clear whether the 0xaa data of this variant is part of the strings
++ * in the tuple, or part of the offset table. It doesn’t really matter. This
++ * is a regression test to check that the code to validate the offset table
++ * doesn’t unconditionally try to access the first entry in the offset table
++ * by subtracting the table size from the end of the GVariant data.
++ *
++ * In this non-normal case, that would result in an address off the start of
++ * the GVariant data, and an out-of-bounds read, because the GVariant is one
++ * byte long, but the offset table is calculated as two bytes long (with 1B
++ * sized entries) from the tuple’s type.
++ */
++ const GVariantType *data_type = G_VARIANT_TYPE ("(sss)");
++ const guint8 data[] = { 0xaa };
++ gsize size = sizeof (data);
++ GVariant *variant = NULL;
++ GVariant *normal_variant = NULL;
++ GVariant *expected = NULL;
++
++ g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2840");
++
++ variant = g_variant_new_from_data (data_type, data, size, FALSE, NULL, NULL);
++ g_assert_nonnull (variant);
++
++ g_assert_false (g_variant_is_normal_form (variant));
++
++ normal_variant = g_variant_get_normal_form (variant);
++ g_assert_nonnull (normal_variant);
++
++ expected = g_variant_new_parsed ("('', '', '')");
++ g_assert_cmpvariant (expected, variant);
++ g_assert_cmpvariant (expected, normal_variant);
++
++ g_variant_unref (expected);
++ g_variant_unref (normal_variant);
++ g_variant_unref (variant);
++}
++
+ /* Test that an otherwise-valid serialised GVariant is considered non-normal if
+ * its offset table entries are too wide.
+ *
+@@ -5813,6 +5874,8 @@ main (int argc, char **argv)
+ test_normal_checking_tuple_offsets3);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets4",
+ test_normal_checking_tuple_offsets4);
++ g_test_add_func ("/gvariant/normal-checking/tuple-offsets5",
++ test_normal_checking_tuple_offsets5);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets/minimal-sized",
+ test_normal_checking_tuple_offsets_minimal_sized);
+ g_test_add_func ("/gvariant/normal-checking/empty-object-path",
+--
+GitLab
+
+
+From 4d0bed8c4690f7a2692474ef6a570bd99ef45ef1 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Thu, 15 Dec 2022 16:49:28 +0000
+Subject: [PATCH 2/2] gvariant: Propagate trust when getting a child of a
+ serialised variant
+
+If a variant is trusted, that means all its children are trusted, so
+ensure that their checked offsets are set as such.
+
+This allows a lot of the offset table checks to be avoided when getting
+children from trusted serialised tuples, which speeds things up.
+
+No unit test is included because this is just a performance fix. If
+there are other slownesses, or regressions, in serialised `GVariant`
+performance, the fuzzing setup will catch them like it did this one.
+
+This change does reduce the time to run the oss-fuzz reproducer from 80s
+to about 0.7s on my machine.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Fixes: #2841
+oss-fuzz#54314
+---
+ glib/gvariant-core.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index 75d23f39af..57bbf88cc9 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -1198,8 +1198,8 @@ g_variant_get_child_value (GVariant *value,
+ child->contents.serialised.bytes =
+ g_bytes_ref (value->contents.serialised.bytes);
+ child->contents.serialised.data = s_child.data;
+- child->contents.serialised.ordered_offsets_up_to = s_child.ordered_offsets_up_to;
+- child->contents.serialised.checked_offsets_up_to = s_child.checked_offsets_up_to;
++ child->contents.serialised.ordered_offsets_up_to = (value->state & STATE_TRUSTED) ? G_MAXSIZE : s_child.ordered_offsets_up_to;
++ child->contents.serialised.checked_offsets_up_to = (value->state & STATE_TRUSTED) ? G_MAXSIZE : s_child.checked_offsets_up_to;
+
+ return child;
+ }
+--
+GitLab
+
+
diff --git a/dev-libs/glib/files/glib-2.74.1-normal-form-handling-gvariant.patch b/dev-libs/glib/files/glib-2.74.1-normal-form-handling-gvariant.patch
new file mode 100644
index 0000000..1d75331
--- /dev/null
+++ b/dev-libs/glib/files/glib-2.74.1-normal-form-handling-gvariant.patch
@@ -0,0 +1,2975 @@
+From 590f7a6b76b5a3695aa562c9c3ab244433b6624b Mon Sep 17 00:00:00 2001
+From: William Manley <will@stb-tester.com>
+Date: Tue, 23 Jun 2020 22:59:58 +0100
+Subject: [PATCH 01/18] gvariant-core: Consolidate construction of
+ `GVariantSerialised`
+
+So I only need to change it in one place.
+
+This introduces no functional changes.
+
+Helps: #2121
+---
+ glib/gvariant-core.c | 49 ++++++++++++++++++++++----------------------
+ 1 file changed, 25 insertions(+), 24 deletions(-)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index ad0bab5b62..70c712ddc0 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -351,6 +351,27 @@ g_variant_ensure_size (GVariant *value)
+ }
+ }
+
++/* < private >
++ * g_variant_to_serialised:
++ * @value: a #GVariant
++ *
++ * Gets a GVariantSerialised for a GVariant in state STATE_SERIALISED.
++ */
++inline static GVariantSerialised
++g_variant_to_serialised (GVariant *value)
++{
++ g_assert (value->state & STATE_SERIALISED);
++ {
++ GVariantSerialised serialised = {
++ value->type_info,
++ (gpointer) value->contents.serialised.data,
++ value->size,
++ value->depth,
++ };
++ return serialised;
++ }
++}
++
+ /* < private >
+ * g_variant_serialise:
+ * @value: a #GVariant
+@@ -1009,16 +1030,8 @@ g_variant_n_children (GVariant *value)
+ g_variant_lock (value);
+
+ if (value->state & STATE_SERIALISED)
+- {
+- GVariantSerialised serialised = {
+- value->type_info,
+- (gpointer) value->contents.serialised.data,
+- value->size,
+- value->depth,
+- };
+-
+- n_children = g_variant_serialised_n_children (serialised);
+- }
++ n_children = g_variant_serialised_n_children (
++ g_variant_to_serialised (value));
+ else
+ n_children = value->contents.tree.n_children;
+
+@@ -1085,12 +1098,7 @@ g_variant_get_child_value (GVariant *value,
+ }
+
+ {
+- GVariantSerialised serialised = {
+- value->type_info,
+- (gpointer) value->contents.serialised.data,
+- value->size,
+- value->depth,
+- };
++ GVariantSerialised serialised = g_variant_to_serialised (value);
+ GVariantSerialised s_child;
+ GVariant *child;
+
+@@ -1203,14 +1211,7 @@ g_variant_is_normal_form (GVariant *value)
+
+ if (value->state & STATE_SERIALISED)
+ {
+- GVariantSerialised serialised = {
+- value->type_info,
+- (gpointer) value->contents.serialised.data,
+- value->size,
+- value->depth
+- };
+-
+- if (g_variant_serialised_is_normal (serialised))
++ if (g_variant_serialised_is_normal (g_variant_to_serialised (value)))
+ value->state |= STATE_TRUSTED;
+ }
+ else
+--
+GitLab
+
+
+From f8f5d8eefa06008aa8fe684069193dc3b1ae1b58 Mon Sep 17 00:00:00 2001
+From: William Manley <will@stb-tester.com>
+Date: Thu, 25 Jun 2020 17:08:21 +0100
+Subject: [PATCH 02/18] gvariant-serialiser: Factor out functions for dealing
+ with framing offsets
+
+This introduces no functional changes.
+
+Helps: #2121
+---
+ glib/gvariant-serialiser.c | 108 +++++++++++++++++++------------------
+ 1 file changed, 57 insertions(+), 51 deletions(-)
+
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index 3af68b79b2..09492e4526 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -635,30 +635,62 @@ gvs_calculate_total_size (gsize body_size,
+ return body_size + 8 * offsets;
+ }
+
++struct Offsets
++{
++ gsize data_size;
++
++ guchar *array;
++ gsize length;
++ guint offset_size;
++
++ gboolean is_normal;
++};
++
+ static gsize
+-gvs_variable_sized_array_n_children (GVariantSerialised value)
++gvs_offsets_get_offset_n (struct Offsets *offsets,
++ gsize n)
++{
++ return gvs_read_unaligned_le (
++ offsets->array + (offsets->offset_size * n), offsets->offset_size);
++}
++
++static struct Offsets
++gvs_variable_sized_array_get_frame_offsets (GVariantSerialised value)
+ {
++ struct Offsets out = { 0, };
+ gsize offsets_array_size;
+- gsize offset_size;
+ gsize last_end;
+
+ if (value.size == 0)
+- return 0;
+-
+- offset_size = gvs_get_offset_size (value.size);
++ {
++ out.is_normal = TRUE;
++ return out;
++ }
+
+- last_end = gvs_read_unaligned_le (value.data + value.size -
+- offset_size, offset_size);
++ out.offset_size = gvs_get_offset_size (value.size);
++ last_end = gvs_read_unaligned_le (value.data + value.size - out.offset_size,
++ out.offset_size);
+
+ if (last_end > value.size)
+- return 0;
++ return out; /* offsets not normal */
+
+ offsets_array_size = value.size - last_end;
+
+- if (offsets_array_size % offset_size)
+- return 0;
++ if (offsets_array_size % out.offset_size)
++ return out; /* offsets not normal */
++
++ out.data_size = last_end;
++ out.array = value.data + last_end;
++ out.length = offsets_array_size / out.offset_size;
++ out.is_normal = TRUE;
+
+- return offsets_array_size / offset_size;
++ return out;
++}
++
++static gsize
++gvs_variable_sized_array_n_children (GVariantSerialised value)
++{
++ return gvs_variable_sized_array_get_frame_offsets (value).length;
+ }
+
+ static GVariantSerialised
+@@ -666,8 +698,9 @@ gvs_variable_sized_array_get_child (GVariantSerialised value,
+ gsize index_)
+ {
+ GVariantSerialised child = { 0, };
+- gsize offset_size;
+- gsize last_end;
++
++ struct Offsets offsets = gvs_variable_sized_array_get_frame_offsets (value);
++
+ gsize start;
+ gsize end;
+
+@@ -675,18 +708,11 @@ gvs_variable_sized_array_get_child (GVariantSerialised value,
+ g_variant_type_info_ref (child.type_info);
+ child.depth = value.depth + 1;
+
+- offset_size = gvs_get_offset_size (value.size);
+-
+- last_end = gvs_read_unaligned_le (value.data + value.size -
+- offset_size, offset_size);
+-
+ if (index_ > 0)
+ {
+ guint alignment;
+
+- start = gvs_read_unaligned_le (value.data + last_end +
+- (offset_size * (index_ - 1)),
+- offset_size);
++ start = gvs_offsets_get_offset_n (&offsets, index_ - 1);
+
+ g_variant_type_info_query (child.type_info, &alignment, NULL);
+ start += (-start) & alignment;
+@@ -694,11 +720,9 @@ gvs_variable_sized_array_get_child (GVariantSerialised value,
+ else
+ start = 0;
+
+- end = gvs_read_unaligned_le (value.data + last_end +
+- (offset_size * index_),
+- offset_size);
++ end = gvs_offsets_get_offset_n (&offsets, index_);
+
+- if (start < end && end <= value.size && end <= last_end)
++ if (start < end && end <= value.size && end <= offsets.data_size)
+ {
+ child.data = value.data + start;
+ child.size = end - start;
+@@ -770,34 +794,16 @@ static gboolean
+ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+ {
+ GVariantSerialised child = { 0, };
+- gsize offsets_array_size;
+- guchar *offsets_array;
+- guint offset_size;
+ guint alignment;
+- gsize last_end;
+- gsize length;
+ gsize offset;
+ gsize i;
+
+- if (value.size == 0)
+- return TRUE;
+-
+- offset_size = gvs_get_offset_size (value.size);
+- last_end = gvs_read_unaligned_le (value.data + value.size -
+- offset_size, offset_size);
++ struct Offsets offsets = gvs_variable_sized_array_get_frame_offsets (value);
+
+- if (last_end > value.size)
++ if (!offsets.is_normal)
+ return FALSE;
+
+- offsets_array_size = value.size - last_end;
+-
+- if (offsets_array_size % offset_size)
+- return FALSE;
+-
+- offsets_array = value.data + value.size - offsets_array_size;
+- length = offsets_array_size / offset_size;
+-
+- if (length == 0)
++ if (value.size != 0 && offsets.length == 0)
+ return FALSE;
+
+ child.type_info = g_variant_type_info_element (value.type_info);
+@@ -805,14 +811,14 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+ child.depth = value.depth + 1;
+ offset = 0;
+
+- for (i = 0; i < length; i++)
++ for (i = 0; i < offsets.length; i++)
+ {
+ gsize this_end;
+
+- this_end = gvs_read_unaligned_le (offsets_array + offset_size * i,
+- offset_size);
++ this_end = gvs_read_unaligned_le (offsets.array + offsets.offset_size * i,
++ offsets.offset_size);
+
+- if (this_end < offset || this_end > last_end)
++ if (this_end < offset || this_end > offsets.data_size)
+ return FALSE;
+
+ while (offset & alignment)
+@@ -834,7 +840,7 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+ offset = this_end;
+ }
+
+- g_assert (offset == last_end);
++ g_assert (offset == offsets.data_size);
+
+ return TRUE;
+ }
+--
+GitLab
+
+
+From 5c27f22aff636766fbc5e49927fd28213629e840 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Tue, 25 Oct 2022 18:05:52 +0100
+Subject: [PATCH 03/18] gvariant: Zero-initialise various GVariantSerialised
+ objects
+
+The following few commits will add a couple of new fields to
+`GVariantSerialised`, and they should be zero-filled by default.
+
+Try and pre-empt that a bit by zero-filling `GVariantSerialised` by
+default in a few places.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2121
+---
+ glib/gvariant.c | 2 +-
+ glib/tests/gvariant.c | 12 ++++++------
+ 2 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index 93eb6cb6bc..6912eebe05 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -6001,7 +6001,7 @@ g_variant_byteswap (GVariant *value)
+ if (alignment)
+ /* (potentially) contains multi-byte numeric data */
+ {
+- GVariantSerialised serialised;
++ GVariantSerialised serialised = { 0, };
+ GVariant *trusted;
+ GBytes *bytes;
+
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 29110e4e24..1a57dc6ef7 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -1440,7 +1440,7 @@ test_maybe (void)
+
+ for (flavour = 0; flavour < 8; flavour += alignment)
+ {
+- GVariantSerialised serialised;
++ GVariantSerialised serialised = { 0, };
+ GVariantSerialised child;
+
+ serialised.type_info = type_info;
+@@ -1564,7 +1564,7 @@ test_array (void)
+
+ for (flavour = 0; flavour < 8; flavour += alignment)
+ {
+- GVariantSerialised serialised;
++ GVariantSerialised serialised = { 0, };
+
+ serialised.type_info = array_info;
+ serialised.data = flavoured_malloc (needed_size, flavour);
+@@ -1728,7 +1728,7 @@ test_tuple (void)
+
+ for (flavour = 0; flavour < 8; flavour += alignment)
+ {
+- GVariantSerialised serialised;
++ GVariantSerialised serialised = { 0, };
+
+ serialised.type_info = type_info;
+ serialised.data = flavoured_malloc (needed_size, flavour);
+@@ -1823,7 +1823,7 @@ test_variant (void)
+
+ for (flavour = 0; flavour < 8; flavour += alignment)
+ {
+- GVariantSerialised serialised;
++ GVariantSerialised serialised = { 0, };
+ GVariantSerialised child;
+
+ serialised.type_info = type_info;
+@@ -2270,7 +2270,7 @@ serialise_tree (TreeInstance *tree,
+ static void
+ test_byteswap (void)
+ {
+- GVariantSerialised one, two;
++ GVariantSerialised one = { 0, }, two = { 0, };
+ TreeInstance *tree;
+
+ tree = tree_instance_new (NULL, 3);
+@@ -2344,7 +2344,7 @@ test_serialiser_children (void)
+ static void
+ test_fuzz (gdouble *fuzziness)
+ {
+- GVariantSerialised serialised;
++ GVariantSerialised serialised = { 0, };
+ TreeInstance *tree;
+
+ /* make an instance */
+--
+GitLab
+
+
+From c8067857f7c8fb369ecb30bb534b018b2e2b8f87 Mon Sep 17 00:00:00 2001
+From: William Manley <will@stb-tester.com>
+Date: Mon, 29 Jun 2020 16:59:44 +0100
+Subject: [PATCH 04/18] =?UTF-8?q?gvariant:=20Don=E2=80=99t=20allow=20child?=
+ =?UTF-8?q?=20elements=20to=20overlap=20with=20each=20other?=
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+If different elements of a variable sized array can overlap with each
+other then we can cause a `GVariant` to normalise to a much larger type.
+
+This commit changes the behaviour of `GVariant` with non-normal form data. If
+an invalid frame offset is found all subsequent elements are given their
+default value.
+
+When retrieving an element at index `n` we scan the frame offsets up to index
+`n` and if they are not in order we return an element with the default value
+for that type. This guarantees that elements don't overlap with each
+other. We remember the offset we've scanned up to so we don't need to
+repeat this work on subsequent accesses. We skip these checks for trusted
+data.
+
+Unfortunately this makes random access of untrusted data O(n) — at least
+on first access. It doesn't affect the algorithmic complexity of accessing
+elements in order, such as when using the `GVariantIter` interface. Also:
+the cost of validation will be amortised as the `GVariant` instance is
+continued to be used.
+
+I've implemented this with 4 different functions, 1 for each element size,
+rather than looping calling `gvs_read_unaligned_le` in the hope that the
+compiler will find it easy to optimise and should produce fairly tight
+code.
+
+Fixes: #2121
+---
+ glib/gvariant-core.c | 35 ++++++++++++++++
+ glib/gvariant-serialiser.c | 86 ++++++++++++++++++++++++++++++++++++--
+ glib/gvariant-serialiser.h | 9 ++++
+ glib/tests/gvariant.c | 45 ++++++++++++++++++++
+ 4 files changed, 172 insertions(+), 3 deletions(-)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index 70c712ddc0..a75e7db9ea 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -67,6 +67,7 @@ struct _GVariant
+ {
+ GBytes *bytes;
+ gconstpointer data;
++ gsize ordered_offsets_up_to;
+ } serialised;
+
+ struct
+@@ -164,6 +165,24 @@ struct _GVariant
+ * if .data pointed to the appropriate number of nul
+ * bytes.
+ *
++ * .ordered_offsets_up_to: If ordered_offsets_up_to == n this means that all
++ * the frame offsets up to and including the frame
++ * offset determining the end of element n are in
++ * order. This guarantees that the bytes of element
++ * n don't overlap with any previous element.
++ *
++ * For trusted data this is set to G_MAXSIZE and we
++ * don't check that the frame offsets are in order.
++ *
++ * Note: This doesn't imply the offsets are good in
++ * any way apart from their ordering. In particular
++ * offsets may be out of bounds for this value or
++ * may imply that the data overlaps the frame
++ * offsets themselves.
++ *
++ * This field is only relevant for arrays of non
++ * fixed width types.
++ *
+ * .tree: Only valid when the instance is in tree form.
+ *
+ * Note that accesses from other threads could result in
+@@ -367,6 +386,7 @@ g_variant_to_serialised (GVariant *value)
+ (gpointer) value->contents.serialised.data,
+ value->size,
+ value->depth,
++ value->contents.serialised.ordered_offsets_up_to,
+ };
+ return serialised;
+ }
+@@ -398,6 +418,7 @@ g_variant_serialise (GVariant *value,
+ serialised.size = value->size;
+ serialised.data = data;
+ serialised.depth = value->depth;
++ serialised.ordered_offsets_up_to = 0;
+
+ children = (gpointer *) value->contents.tree.children;
+ n_children = value->contents.tree.n_children;
+@@ -441,6 +462,15 @@ g_variant_fill_gvs (GVariantSerialised *serialised,
+ g_assert (serialised->size == value->size);
+ serialised->depth = value->depth;
+
++ if (value->state & STATE_SERIALISED)
++ {
++ serialised->ordered_offsets_up_to = value->contents.serialised.ordered_offsets_up_to;
++ }
++ else
++ {
++ serialised->ordered_offsets_up_to = 0;
++ }
++
+ if (serialised->data)
+ /* g_variant_store() is a public API, so it
+ * it will reacquire the lock if it needs to.
+@@ -483,6 +513,7 @@ g_variant_ensure_serialised (GVariant *value)
+ bytes = g_bytes_new_take (data, value->size);
+ value->contents.serialised.data = g_bytes_get_data (bytes, NULL);
+ value->contents.serialised.bytes = bytes;
++ value->contents.serialised.ordered_offsets_up_to = G_MAXSIZE;
+ value->state |= STATE_SERIALISED;
+ }
+ }
+@@ -563,6 +594,7 @@ g_variant_new_from_bytes (const GVariantType *type,
+ serialised.type_info = value->type_info;
+ serialised.data = (guchar *) g_bytes_get_data (bytes, &serialised.size);
+ serialised.depth = 0;
++ serialised.ordered_offsets_up_to = trusted ? G_MAXSIZE : 0;
+
+ if (!g_variant_serialised_check (serialised))
+ {
+@@ -613,6 +645,8 @@ g_variant_new_from_bytes (const GVariantType *type,
+ value->contents.serialised.data = g_bytes_get_data (bytes, &value->size);
+ }
+
++ value->contents.serialised.ordered_offsets_up_to = trusted ? G_MAXSIZE : 0;
++
+ g_clear_pointer (&owned_bytes, g_bytes_unref);
+
+ return value;
+@@ -1132,6 +1166,7 @@ g_variant_get_child_value (GVariant *value,
+ child->contents.serialised.bytes =
+ g_bytes_ref (value->contents.serialised.bytes);
+ child->contents.serialised.data = s_child.data;
++ child->contents.serialised.ordered_offsets_up_to = s_child.ordered_offsets_up_to;
+
+ return child;
+ }
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index 09492e4526..1aa2b8fa9b 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -1,6 +1,7 @@
+ /*
+ * Copyright © 2007, 2008 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
++ * Copyright © 2020 William Manley
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+@@ -266,6 +267,7 @@ gvs_fixed_sized_maybe_get_child (GVariantSerialised value,
+ value.type_info = g_variant_type_info_element (value.type_info);
+ g_variant_type_info_ref (value.type_info);
+ value.depth++;
++ value.ordered_offsets_up_to = 0;
+
+ return value;
+ }
+@@ -297,7 +299,7 @@ gvs_fixed_sized_maybe_serialise (GVariantSerialised value,
+ {
+ if (n_children)
+ {
+- GVariantSerialised child = { NULL, value.data, value.size, value.depth + 1 };
++ GVariantSerialised child = { NULL, value.data, value.size, value.depth + 1, 0 };
+
+ gvs_filler (&child, children[0]);
+ }
+@@ -319,6 +321,7 @@ gvs_fixed_sized_maybe_is_normal (GVariantSerialised value)
+ /* proper element size: "Just". recurse to the child. */
+ value.type_info = g_variant_type_info_element (value.type_info);
+ value.depth++;
++ value.ordered_offsets_up_to = 0;
+
+ return g_variant_serialised_is_normal (value);
+ }
+@@ -360,6 +363,7 @@ gvs_variable_sized_maybe_get_child (GVariantSerialised value,
+ value.data = NULL;
+
+ value.depth++;
++ value.ordered_offsets_up_to = 0;
+
+ return value;
+ }
+@@ -390,7 +394,7 @@ gvs_variable_sized_maybe_serialise (GVariantSerialised value,
+ {
+ if (n_children)
+ {
+- GVariantSerialised child = { NULL, value.data, value.size - 1, value.depth + 1 };
++ GVariantSerialised child = { NULL, value.data, value.size - 1, value.depth + 1, 0 };
+
+ /* write the data for the child. */
+ gvs_filler (&child, children[0]);
+@@ -410,6 +414,7 @@ gvs_variable_sized_maybe_is_normal (GVariantSerialised value)
+ value.type_info = g_variant_type_info_element (value.type_info);
+ value.size--;
+ value.depth++;
++ value.ordered_offsets_up_to = 0;
+
+ return g_variant_serialised_is_normal (value);
+ }
+@@ -693,6 +698,32 @@ gvs_variable_sized_array_n_children (GVariantSerialised value)
+ return gvs_variable_sized_array_get_frame_offsets (value).length;
+ }
+
++/* Find the index of the first out-of-order element in @data, assuming that
++ * @data is an array of elements of given @type, starting at index @start and
++ * containing a further @len-@start elements. */
++#define DEFINE_FIND_UNORDERED(type) \
++ static gsize \
++ find_unordered_##type (const guint8 *data, gsize start, gsize len) \
++ { \
++ gsize off; \
++ type current, previous; \
++ \
++ memcpy (&previous, data + start * sizeof (current), sizeof (current)); \
++ for (off = (start + 1) * sizeof (current); off < len * sizeof (current); off += sizeof (current)) \
++ { \
++ memcpy (¤t, data + off, sizeof (current)); \
++ if (current < previous) \
++ break; \
++ previous = current; \
++ } \
++ return off / sizeof (current) - 1; \
++ }
++
++DEFINE_FIND_UNORDERED (guint8);
++DEFINE_FIND_UNORDERED (guint16);
++DEFINE_FIND_UNORDERED (guint32);
++DEFINE_FIND_UNORDERED (guint64);
++
+ static GVariantSerialised
+ gvs_variable_sized_array_get_child (GVariantSerialised value,
+ gsize index_)
+@@ -708,6 +739,49 @@ gvs_variable_sized_array_get_child (GVariantSerialised value,
+ g_variant_type_info_ref (child.type_info);
+ child.depth = value.depth + 1;
+
++ /* If the requested @index_ is beyond the set of indices whose framing offsets
++ * have been checked, check the remaining offsets to see whether they’re
++ * normal (in order, no overlapping array elements). */
++ if (index_ > value.ordered_offsets_up_to)
++ {
++ switch (offsets.offset_size)
++ {
++ case 1:
++ {
++ value.ordered_offsets_up_to = find_unordered_guint8 (
++ offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ break;
++ }
++ case 2:
++ {
++ value.ordered_offsets_up_to = find_unordered_guint16 (
++ offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ break;
++ }
++ case 4:
++ {
++ value.ordered_offsets_up_to = find_unordered_guint32 (
++ offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ break;
++ }
++ case 8:
++ {
++ value.ordered_offsets_up_to = find_unordered_guint64 (
++ offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ break;
++ }
++ default:
++ /* gvs_get_offset_size() only returns maximum 8 */
++ g_assert_not_reached ();
++ }
++ }
++
++ if (index_ > value.ordered_offsets_up_to)
++ {
++ /* Offsets are invalid somewhere, so return an empty child. */
++ return child;
++ }
++
+ if (index_ > 0)
+ {
+ guint alignment;
+@@ -842,6 +916,9 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+
+ g_assert (offset == offsets.data_size);
+
++ /* All offsets have now been checked. */
++ value.ordered_offsets_up_to = G_MAXSIZE;
++
+ return TRUE;
+ }
+
+@@ -1074,7 +1151,7 @@ gvs_tuple_is_normal (GVariantSerialised value)
+ for (i = 0; i < length; i++)
+ {
+ const GVariantMemberInfo *member_info;
+- GVariantSerialised child;
++ GVariantSerialised child = { 0, };
+ gsize fixed_size;
+ guint alignment;
+ gsize end;
+@@ -1134,6 +1211,9 @@ gvs_tuple_is_normal (GVariantSerialised value)
+ offset = end;
+ }
+
++ /* All element bounds have been checked above. */
++ value.ordered_offsets_up_to = G_MAXSIZE;
++
+ {
+ gsize fixed_size;
+ guint alignment;
+diff --git a/glib/gvariant-serialiser.h b/glib/gvariant-serialiser.h
+index 6ced7e3d6c..a1bccb834b 100644
+--- a/glib/gvariant-serialiser.h
++++ b/glib/gvariant-serialiser.h
+@@ -31,6 +31,15 @@ typedef struct
+ guchar *data;
+ gsize size;
+ gsize depth; /* same semantics as GVariant.depth */
++
++ /* If ordered_offsets_up_to == n this means that all the frame offsets up to and
++ * including the frame offset determining the end of element n are in order.
++ * This guarantees that the bytes of element n don't overlap with any previous
++ * element.
++ *
++ * This is both read and set by g_variant_serialised_get_child for arrays of
++ * non-fixed-width types */
++ gsize ordered_offsets_up_to;
+ } GVariantSerialised;
+
+ /* deserialization */
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 1a57dc6ef7..c759e8d701 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -1,5 +1,6 @@
+ /*
+ * Copyright © 2010 Codethink Limited
++ * Copyright © 2020 William Manley
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+@@ -1281,6 +1282,7 @@ random_instance_filler (GVariantSerialised *serialised,
+ serialised->size = instance->size;
+
+ serialised->depth = 0;
++ serialised->ordered_offsets_up_to = 0;
+
+ g_assert_true (serialised->type_info == instance->type_info);
+ g_assert_cmpuint (serialised->size, ==, instance->size);
+@@ -5131,6 +5133,47 @@ test_normal_checking_array_offsets (void)
+ g_variant_unref (variant);
+ }
+
++/* This is a regression test that we can't have non-normal values that take up
++ * significantly more space than the normal equivalent, by specifying the
++ * offset table entries so that array elements overlap.
++ *
++ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_832242 */
++static void
++test_normal_checking_array_offsets2 (void)
++{
++ const guint8 data[] = {
++ 'h', 'i', '\0',
++ 0x03, 0x00, 0x03,
++ 0x06, 0x00, 0x06,
++ 0x09, 0x00, 0x09,
++ 0x0c, 0x00, 0x0c,
++ 0x0f, 0x00, 0x0f,
++ 0x12, 0x00, 0x12,
++ 0x15, 0x00, 0x15,
++ };
++ gsize size = sizeof (data);
++ const GVariantType *aaaaaaas = G_VARIANT_TYPE ("aaaaaaas");
++ GVariant *variant = NULL;
++ GVariant *normal_variant = NULL;
++ GVariant *expected = NULL;
++
++ variant = g_variant_new_from_data (aaaaaaas, data, size, FALSE, NULL, NULL);
++ g_assert_nonnull (variant);
++
++ normal_variant = g_variant_get_normal_form (variant);
++ g_assert_nonnull (normal_variant);
++ g_assert_cmpuint (g_variant_get_size (normal_variant), <=, size * 2);
++
++ expected = g_variant_new_parsed (
++ "[[[[[[['hi', '', ''], [], []], [], []], [], []], [], []], [], []], [], []]");
++ g_assert_cmpvariant (expected, variant);
++ g_assert_cmpvariant (expected, normal_variant);
++
++ g_variant_unref (expected);
++ g_variant_unref (normal_variant);
++ g_variant_unref (variant);
++}
++
+ /* Test that a tuple with invalidly large values in its offset table is
+ * normalised successfully without looping infinitely. */
+ static void
+@@ -5299,6 +5342,8 @@ main (int argc, char **argv)
+ test_normal_checking_tuples);
+ g_test_add_func ("/gvariant/normal-checking/array-offsets",
+ test_normal_checking_array_offsets);
++ g_test_add_func ("/gvariant/normal-checking/array-offsets2",
++ test_normal_checking_array_offsets2);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets",
+ test_normal_checking_tuple_offsets);
+ g_test_add_func ("/gvariant/normal-checking/empty-object-path",
+--
+GitLab
+
+
+From 66e7c10aa1ec3102c49186f83671a5f0461ffbc0 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Fri, 7 Jan 2022 15:03:52 +0000
+Subject: [PATCH 05/18] gvariant-serialiser: Factor out code to get bounds of a
+ tuple member
+
+This introduces no functional changes.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2121
+---
+ glib/gvariant-serialiser.c | 73 ++++++++++++++++++++++++--------------
+ 1 file changed, 46 insertions(+), 27 deletions(-)
+
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index 1aa2b8fa9b..a518117e3c 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -944,6 +944,51 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+ * for the tuple. See the notes in gvarianttypeinfo.h.
+ */
+
++static void
++gvs_tuple_get_member_bounds (GVariantSerialised value,
++ gsize index_,
++ gsize offset_size,
++ gsize *out_member_start,
++ gsize *out_member_end)
++{
++ const GVariantMemberInfo *member_info;
++ gsize member_start, member_end;
++
++ member_info = g_variant_type_info_member_info (value.type_info, index_);
++
++ if (member_info->i + 1)
++ member_start = gvs_read_unaligned_le (value.data + value.size -
++ offset_size * (member_info->i + 1),
++ offset_size);
++ else
++ member_start = 0;
++
++ member_start += member_info->a;
++ member_start &= member_info->b;
++ member_start |= member_info->c;
++
++ if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_LAST)
++ member_end = value.size - offset_size * (member_info->i + 1);
++
++ else if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_FIXED)
++ {
++ gsize fixed_size;
++
++ g_variant_type_info_query (member_info->type_info, NULL, &fixed_size);
++ member_end = member_start + fixed_size;
++ }
++
++ else /* G_VARIANT_MEMBER_ENDING_OFFSET */
++ member_end = gvs_read_unaligned_le (value.data + value.size -
++ offset_size * (member_info->i + 2),
++ offset_size);
++
++ if (out_member_start != NULL)
++ *out_member_start = member_start;
++ if (out_member_end != NULL)
++ *out_member_end = member_end;
++}
++
+ static gsize
+ gvs_tuple_n_children (GVariantSerialised value)
+ {
+@@ -999,33 +1044,7 @@ gvs_tuple_get_child (GVariantSerialised value,
+ }
+ }
+
+- if (member_info->i + 1)
+- start = gvs_read_unaligned_le (value.data + value.size -
+- offset_size * (member_info->i + 1),
+- offset_size);
+- else
+- start = 0;
+-
+- start += member_info->a;
+- start &= member_info->b;
+- start |= member_info->c;
+-
+- if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_LAST)
+- end = value.size - offset_size * (member_info->i + 1);
+-
+- else if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_FIXED)
+- {
+- gsize fixed_size;
+-
+- g_variant_type_info_query (child.type_info, NULL, &fixed_size);
+- end = start + fixed_size;
+- child.size = fixed_size;
+- }
+-
+- else /* G_VARIANT_MEMBER_ENDING_OFFSET */
+- end = gvs_read_unaligned_le (value.data + value.size -
+- offset_size * (member_info->i + 2),
+- offset_size);
++ gvs_tuple_get_member_bounds (value, index_, offset_size, &start, &end);
+
+ /* The child should not extend into the offset table. */
+ if (index_ != g_variant_type_info_n_members (value.type_info) - 1)
+--
+GitLab
+
+
+From a62a6b5d3e53b30a4628db2a077ab9ed03605748 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Fri, 7 Jan 2022 16:37:29 +0000
+Subject: [PATCH 06/18] gvariant-serialiser: Rework child size calculation
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This reduces a few duplicate calls to `g_variant_type_info_query()` and
+explains why they’re needed.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2121
+---
+ glib/gvariant-serialiser.c | 31 +++++++++----------------------
+ 1 file changed, 9 insertions(+), 22 deletions(-)
+
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index a518117e3c..d32e255480 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -1009,14 +1009,18 @@ gvs_tuple_get_child (GVariantSerialised value,
+ child.depth = value.depth + 1;
+ offset_size = gvs_get_offset_size (value.size);
+
++ /* Ensure the size is set for fixed-sized children, or
++ * g_variant_serialised_check() will fail, even if we return
++ * (child.data == NULL) to indicate an error. */
++ if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_FIXED)
++ g_variant_type_info_query (child.type_info, NULL, &child.size);
++
+ /* tuples are the only (potentially) fixed-sized containers, so the
+ * only ones that have to deal with the possibility of having %NULL
+ * data with a non-zero %size if errors occurred elsewhere.
+ */
+ if G_UNLIKELY (value.data == NULL && value.size != 0)
+ {
+- g_variant_type_info_query (child.type_info, NULL, &child.size);
+-
+ /* this can only happen in fixed-sized tuples,
+ * so the child must also be fixed sized.
+ */
+@@ -1034,29 +1038,12 @@ gvs_tuple_get_child (GVariantSerialised value,
+ else
+ {
+ if (offset_size * (member_info->i + 1) > value.size)
+- {
+- /* if the child is fixed size, return its size.
+- * if child is not fixed-sized, return size = 0.
+- */
+- g_variant_type_info_query (child.type_info, NULL, &child.size);
+-
+- return child;
+- }
++ return child;
+ }
+
+- gvs_tuple_get_member_bounds (value, index_, offset_size, &start, &end);
+-
+ /* The child should not extend into the offset table. */
+- if (index_ != g_variant_type_info_n_members (value.type_info) - 1)
+- {
+- GVariantSerialised last_child;
+- last_child = gvs_tuple_get_child (value,
+- g_variant_type_info_n_members (value.type_info) - 1);
+- last_end = last_child.data + last_child.size - value.data;
+- g_variant_type_info_unref (last_child.type_info);
+- }
+- else
+- last_end = end;
++ gvs_tuple_get_member_bounds (value, index_, offset_size, &start, &end);
++ gvs_tuple_get_member_bounds (value, g_variant_type_info_n_members (value.type_info) - 1, offset_size, NULL, &last_end);
+
+ if (start < end && end <= value.size && end <= last_end)
+ {
+--
+GitLab
+
+
+From 2d55b3b74b1bc256e91a9d0d59120570376e6acc Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Fri, 7 Jan 2022 16:42:14 +0000
+Subject: [PATCH 07/18] =?UTF-8?q?gvariant:=20Don=E2=80=99t=20allow=20child?=
+ =?UTF-8?q?=20elements=20of=20a=20tuple=20to=20overlap=20each=20other?=
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This is similar to the earlier commit which prevents child elements of a
+variable-sized array from overlapping each other, but this time for
+tuples. It is based heavily on ideas by William Manley.
+
+Tuples are slightly different from variable-sized arrays in that they
+contain a mixture of fixed and variable sized elements. All but one of
+the variable sized elements have an entry in the frame offsets table.
+This means that if we were to just check the ordering of the frame
+offsets table, the variable sized elements could still overlap
+interleaving fixed sized elements, which would be bad.
+
+Therefore we have to check the elements rather than the frame offsets.
+
+The logic of checking the elements up to the index currently being
+requested, and caching the result in `ordered_offsets_up_to`, means that
+the algorithmic cost implications are the same for this commit as for
+variable-sized arrays: an O(N) cost for these checks is amortised out
+over N accesses to O(1) per access.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Fixes: #2121
+---
+ glib/gvariant-core.c | 6 +-
+ glib/gvariant-serialiser.c | 40 ++++++++
+ glib/gvariant-serialiser.h | 7 +-
+ glib/gvariant.c | 1 +
+ glib/tests/gvariant.c | 181 +++++++++++++++++++++++++++++++++++++
+ 5 files changed, 232 insertions(+), 3 deletions(-)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index a75e7db9ea..c0e1931c39 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -1,6 +1,7 @@
+ /*
+ * Copyright © 2007, 2008 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
++ * Copyright © 2022 Endless OS Foundation, LLC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+@@ -181,7 +182,7 @@ struct _GVariant
+ * offsets themselves.
+ *
+ * This field is only relevant for arrays of non
+- * fixed width types.
++ * fixed width types and for tuples.
+ *
+ * .tree: Only valid when the instance is in tree form.
+ *
+@@ -1141,6 +1142,9 @@ g_variant_get_child_value (GVariant *value,
+ */
+ s_child = g_variant_serialised_get_child (serialised, index_);
+
++ /* Update the cached ordered_offsets_up_to, since @serialised will be thrown away when this function exits */
++ value->contents.serialised.ordered_offsets_up_to = MAX (value->contents.serialised.ordered_offsets_up_to, serialised.ordered_offsets_up_to);
++
+ /* Check whether this would cause nesting too deep. If so, return a fake
+ * child. The only situation we expect this to happen in is with a variant,
+ * as all other deeply-nested types have a static type, and hence should
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index d32e255480..fda59d6843 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -944,6 +944,10 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+ * for the tuple. See the notes in gvarianttypeinfo.h.
+ */
+
++/* Note: This doesn’t guarantee that @out_member_end >= @out_member_start; that
++ * condition may not hold true for invalid serialised variants. The caller is
++ * responsible for checking the returned values and handling invalid ones
++ * appropriately. */
+ static void
+ gvs_tuple_get_member_bounds (GVariantSerialised value,
+ gsize index_,
+@@ -1030,6 +1034,42 @@ gvs_tuple_get_child (GVariantSerialised value,
+ return child;
+ }
+
++ /* If the requested @index_ is beyond the set of indices whose framing offsets
++ * have been checked, check the remaining offsets to see whether they’re
++ * normal (in order, no overlapping tuple elements).
++ *
++ * Unlike the checks in gvs_variable_sized_array_get_child(), we have to check
++ * all the tuple *elements* here, not just all the framing offsets, since
++ * tuples contain a mix of elements which use framing offsets and ones which
++ * don’t. None of them are allowed to overlap. */
++ if (index_ > value.ordered_offsets_up_to)
++ {
++ gsize i, prev_i_end = 0;
++
++ if (value.ordered_offsets_up_to > 0)
++ gvs_tuple_get_member_bounds (value, value.ordered_offsets_up_to - 1, offset_size, NULL, &prev_i_end);
++
++ for (i = value.ordered_offsets_up_to; i <= index_; i++)
++ {
++ gsize i_start, i_end;
++
++ gvs_tuple_get_member_bounds (value, i, offset_size, &i_start, &i_end);
++
++ if (i_start > i_end || i_start < prev_i_end || i_end > value.size)
++ break;
++
++ prev_i_end = i_end;
++ }
++
++ value.ordered_offsets_up_to = i - 1;
++ }
++
++ if (index_ > value.ordered_offsets_up_to)
++ {
++ /* Offsets are invalid somewhere, so return an empty child. */
++ return child;
++ }
++
+ if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_OFFSET)
+ {
+ if (offset_size * (member_info->i + 2) > value.size)
+diff --git a/glib/gvariant-serialiser.h b/glib/gvariant-serialiser.h
+index a1bccb834b..661bd8fd1b 100644
+--- a/glib/gvariant-serialiser.h
++++ b/glib/gvariant-serialiser.h
+@@ -37,8 +37,11 @@ typedef struct
+ * This guarantees that the bytes of element n don't overlap with any previous
+ * element.
+ *
+- * This is both read and set by g_variant_serialised_get_child for arrays of
+- * non-fixed-width types */
++ * This is both read and set by g_variant_serialised_get_child() for arrays of
++ * non-fixed-width types, and for tuples.
++ *
++ * Even when dealing with tuples, @ordered_offsets_up_to is an element index,
++ * rather than an index into the frame offsets. */
+ gsize ordered_offsets_up_to;
+ } GVariantSerialised;
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index 6912eebe05..16af7a5678 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -6010,6 +6010,7 @@ g_variant_byteswap (GVariant *value)
+ serialised.size = g_variant_get_size (trusted);
+ serialised.data = g_malloc (serialised.size);
+ serialised.depth = g_variant_get_depth (trusted);
++ serialised.ordered_offsets_up_to = G_MAXSIZE; /* operating on the normal form */
+ g_variant_store (trusted, serialised.data);
+ g_variant_unref (trusted);
+
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index c759e8d701..0cb69d4b70 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -1,6 +1,7 @@
+ /*
+ * Copyright © 2010 Codethink Limited
+ * Copyright © 2020 William Manley
++ * Copyright © 2022 Endless OS Foundation, LLC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+@@ -1449,6 +1450,7 @@ test_maybe (void)
+ serialised.data = flavoured_malloc (needed_size, flavour);
+ serialised.size = needed_size;
+ serialised.depth = 0;
++ serialised.ordered_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised,
+ random_instance_filler,
+@@ -1572,6 +1574,7 @@ test_array (void)
+ serialised.data = flavoured_malloc (needed_size, flavour);
+ serialised.size = needed_size;
+ serialised.depth = 0;
++ serialised.ordered_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised, random_instance_filler,
+ (gpointer *) instances, n_children);
+@@ -1736,6 +1739,7 @@ test_tuple (void)
+ serialised.data = flavoured_malloc (needed_size, flavour);
+ serialised.size = needed_size;
+ serialised.depth = 0;
++ serialised.ordered_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised, random_instance_filler,
+ (gpointer *) instances, n_children);
+@@ -1832,6 +1836,7 @@ test_variant (void)
+ serialised.data = flavoured_malloc (needed_size, flavour);
+ serialised.size = needed_size;
+ serialised.depth = 0;
++ serialised.ordered_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised, random_instance_filler,
+ (gpointer *) &instance, 1);
+@@ -5198,6 +5203,176 @@ test_normal_checking_tuple_offsets (void)
+ g_variant_unref (variant);
+ }
+
++/* This is a regression test that we can't have non-normal values that take up
++ * significantly more space than the normal equivalent, by specifying the
++ * offset table entries so that tuple elements overlap.
++ *
++ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_838503 and
++ * https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_838513 */
++static void
++test_normal_checking_tuple_offsets2 (void)
++{
++ const GVariantType *data_type = G_VARIANT_TYPE ("(yyaiyyaiyy)");
++ const guint8 data[] = {
++ 0x12, 0x34, 0x56, 0x78, 0x01,
++ /*
++ ^───────────────────┘
++
++ ^^^^^^^^^^ 1st yy
++ ^^^^^^^^^^ 2nd yy
++ ^^^^^^^^^^ 3rd yy
++ ^^^^ Framing offsets
++ */
++
++ /* If this variant was encoded normally, it would be something like this:
++ * 0x12, 0x34, pad, pad, [array bytes], 0x56, 0x78, pad, pad, [array bytes], 0x9A, 0xBC, 0xXX
++ * ^─────────────────────────────────────────────────────┘
++ *
++ * ^^^^^^^^^^ 1st yy
++ * ^^^^^^^^^^ 2nd yy
++ * ^^^^^^^^^^ 3rd yy
++ * ^^^^ Framing offsets
++ */
++ };
++ gsize size = sizeof (data);
++ GVariant *variant = NULL;
++ GVariant *normal_variant = NULL;
++ GVariant *expected = NULL;
++
++ variant = g_variant_new_from_data (data_type, data, size, FALSE, NULL, NULL);
++ g_assert_nonnull (variant);
++
++ normal_variant = g_variant_get_normal_form (variant);
++ g_assert_nonnull (normal_variant);
++ g_assert_cmpuint (g_variant_get_size (normal_variant), <=, size * 3);
++
++ expected = g_variant_new_parsed (
++ "@(yyaiyyaiyy) (0x12, 0x34, [], 0x00, 0x00, [], 0x00, 0x00)");
++ g_assert_cmpvariant (expected, variant);
++ g_assert_cmpvariant (expected, normal_variant);
++
++ g_variant_unref (expected);
++ g_variant_unref (normal_variant);
++ g_variant_unref (variant);
++}
++
++/* This is a regression test that overlapping entries in the offset table are
++ * decoded consistently, even though they’re non-normal.
++ *
++ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_910935 */
++static void
++test_normal_checking_tuple_offsets3 (void)
++{
++ /* The expected decoding of this non-normal byte stream is complex. See
++ * section 2.7.3 (Handling Non-Normal Serialised Data) of the GVariant
++ * specification.
++ *
++ * The rule “Child Values Overlapping Framing Offsets” from the specification
++ * says that the first `ay` must be decoded as `[0x01]` even though it
++ * overlaps the first byte of the offset table. However, since commit
++ * 7eedcd76f7d5b8c98fa60013e1fe6e960bf19df3, GLib explicitly doesn’t allow
++ * this as it’s exploitable. So the first `ay` must be given a default value.
++ *
++ * The second and third `ay`s must be given default values because of rule
++ * “End Boundary Precedes Start Boundary”.
++ *
++ * The `i` must be given a default value because of rule “Start or End
++ * Boundary of a Child Falls Outside the Container”.
++ */
++ const GVariantType *data_type = G_VARIANT_TYPE ("(ayayiay)");
++ const guint8 data[] = {
++ 0x01, 0x00, 0x02,
++ /*
++ ^──┘
++
++ ^^^^^^^^^^ 1st ay, bytes 0-2 (but given a default value anyway, see above)
++ 2nd ay, bytes 2-0
++ i, bytes 0-4
++ 3rd ay, bytes 4-1
++ ^^^^^^^^^^ Framing offsets
++ */
++ };
++ gsize size = sizeof (data);
++ GVariant *variant = NULL;
++ GVariant *normal_variant = NULL;
++ GVariant *expected = NULL;
++
++ variant = g_variant_new_from_data (data_type, data, size, FALSE, NULL, NULL);
++ g_assert_nonnull (variant);
++
++ g_assert_false (g_variant_is_normal_form (variant));
++
++ normal_variant = g_variant_get_normal_form (variant);
++ g_assert_nonnull (normal_variant);
++ g_assert_cmpuint (g_variant_get_size (normal_variant), <=, size * 3);
++
++ expected = g_variant_new_parsed ("@(ayayiay) ([], [], 0, [])");
++ g_assert_cmpvariant (expected, variant);
++ g_assert_cmpvariant (expected, normal_variant);
++
++ g_variant_unref (expected);
++ g_variant_unref (normal_variant);
++ g_variant_unref (variant);
++}
++
++/* This is a regression test that overlapping entries in the offset table are
++ * decoded consistently, even though they’re non-normal.
++ *
++ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_910935 */
++static void
++test_normal_checking_tuple_offsets4 (void)
++{
++ /* The expected decoding of this non-normal byte stream is complex. See
++ * section 2.7.3 (Handling Non-Normal Serialised Data) of the GVariant
++ * specification.
++ *
++ * The rule “Child Values Overlapping Framing Offsets” from the specification
++ * says that the first `ay` must be decoded as `[0x01]` even though it
++ * overlaps the first byte of the offset table. However, since commit
++ * 7eedcd76f7d5b8c98fa60013e1fe6e960bf19df3, GLib explicitly doesn’t allow
++ * this as it’s exploitable. So the first `ay` must be given a default value.
++ *
++ * The second `ay` must be given a default value because of rule “End Boundary
++ * Precedes Start Boundary”.
++ *
++ * The third `ay` must be given a default value because its framing offsets
++ * overlap that of the first `ay`.
++ */
++ const GVariantType *data_type = G_VARIANT_TYPE ("(ayayay)");
++ const guint8 data[] = {
++ 0x01, 0x00, 0x02,
++ /*
++ ^──┘
++
++ ^^^^^^^^^^ 1st ay, bytes 0-2 (but given a default value anyway, see above)
++ 2nd ay, bytes 2-0
++ 3rd ay, bytes 0-1
++ ^^^^^^^^^^ Framing offsets
++ */
++ };
++ gsize size = sizeof (data);
++ GVariant *variant = NULL;
++ GVariant *normal_variant = NULL;
++ GVariant *expected = NULL;
++
++ variant = g_variant_new_from_data (data_type, data, size, FALSE, NULL, NULL);
++ g_assert_nonnull (variant);
++
++ g_assert_false (g_variant_is_normal_form (variant));
++
++ normal_variant = g_variant_get_normal_form (variant);
++ g_assert_nonnull (normal_variant);
++ g_assert_cmpuint (g_variant_get_size (normal_variant), <=, size * 3);
++
++ expected = g_variant_new_parsed ("@(ayayay) ([], [], [])");
++ g_assert_cmpvariant (expected, variant);
++ g_assert_cmpvariant (expected, normal_variant);
++
++ g_variant_unref (expected);
++ g_variant_unref (normal_variant);
++ g_variant_unref (variant);
++}
++
+ /* Test that an empty object path is normalised successfully to the base object
+ * path, ‘/’. */
+ static void
+@@ -5346,6 +5521,12 @@ main (int argc, char **argv)
+ test_normal_checking_array_offsets2);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets",
+ test_normal_checking_tuple_offsets);
++ g_test_add_func ("/gvariant/normal-checking/tuple-offsets2",
++ test_normal_checking_tuple_offsets2);
++ g_test_add_func ("/gvariant/normal-checking/tuple-offsets3",
++ test_normal_checking_tuple_offsets3);
++ g_test_add_func ("/gvariant/normal-checking/tuple-offsets4",
++ test_normal_checking_tuple_offsets4);
+ g_test_add_func ("/gvariant/normal-checking/empty-object-path",
+ test_normal_checking_empty_object_path);
+
+--
+GitLab
+
+
+From a6cb880af0a0932493ba096f01990e694e2c5b72 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Tue, 25 Oct 2022 15:14:14 +0100
+Subject: [PATCH 08/18] gvariant: Track checked and ordered offsets
+ independently
+
+The past few commits introduced the concept of known-good offsets in the
+offset table (which is used for variable-width arrays and tuples).
+Good offsets are ones which are non-overlapping with all the previous
+offsets in the table.
+
+If a bad offset is encountered when indexing into the array or tuple,
+the cached known-good offset index will not be increased. In this way,
+all child variants at and beyond the first bad offset can be returned as
+default values rather than dereferencing potentially invalid data.
+
+In this case, there was no information about the fact that the indexes
+between the highest known-good index and the requested one had been
+checked already. That could lead to a pathological case where an offset
+table with an invalid first offset is repeatedly checked in full when
+trying to access higher-indexed children.
+
+Avoid that by storing the index of the highest checked offset in the
+table, as well as the index of the highest good/ordered offset.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2121
+---
+ glib/gvariant-core.c | 28 ++++++++++++++++++++++++
+ glib/gvariant-serialiser.c | 44 +++++++++++++++++++++++++++-----------
+ glib/gvariant-serialiser.h | 9 ++++++++
+ glib/gvariant.c | 1 +
+ glib/tests/gvariant.c | 5 +++++
+ 5 files changed, 75 insertions(+), 12 deletions(-)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index c0e1931c39..e61242f763 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -69,6 +69,7 @@ struct _GVariant
+ GBytes *bytes;
+ gconstpointer data;
+ gsize ordered_offsets_up_to;
++ gsize checked_offsets_up_to;
+ } serialised;
+
+ struct
+@@ -184,6 +185,24 @@ struct _GVariant
+ * This field is only relevant for arrays of non
+ * fixed width types and for tuples.
+ *
++ * .checked_offsets_up_to: Similarly to .ordered_offsets_up_to, this stores
++ * the index of the highest element, n, whose frame
++ * offsets (and all the preceding frame offsets)
++ * have been checked for validity.
++ *
++ * It is always the case that
++ * .checked_offsets_up_to ≥ .ordered_offsets_up_to.
++ *
++ * If .checked_offsets_up_to == .ordered_offsets_up_to,
++ * then a bad offset has not been found so far.
++ *
++ * If .checked_offsets_up_to > .ordered_offsets_up_to,
++ * then a bad offset has been found at
++ * (.ordered_offsets_up_to + 1).
++ *
++ * This field is only relevant for arrays of non
++ * fixed width types and for tuples.
++ *
+ * .tree: Only valid when the instance is in tree form.
+ *
+ * Note that accesses from other threads could result in
+@@ -388,6 +407,7 @@ g_variant_to_serialised (GVariant *value)
+ value->size,
+ value->depth,
+ value->contents.serialised.ordered_offsets_up_to,
++ value->contents.serialised.checked_offsets_up_to,
+ };
+ return serialised;
+ }
+@@ -420,6 +440,7 @@ g_variant_serialise (GVariant *value,
+ serialised.data = data;
+ serialised.depth = value->depth;
+ serialised.ordered_offsets_up_to = 0;
++ serialised.checked_offsets_up_to = 0;
+
+ children = (gpointer *) value->contents.tree.children;
+ n_children = value->contents.tree.n_children;
+@@ -466,10 +487,12 @@ g_variant_fill_gvs (GVariantSerialised *serialised,
+ if (value->state & STATE_SERIALISED)
+ {
+ serialised->ordered_offsets_up_to = value->contents.serialised.ordered_offsets_up_to;
++ serialised->checked_offsets_up_to = value->contents.serialised.checked_offsets_up_to;
+ }
+ else
+ {
+ serialised->ordered_offsets_up_to = 0;
++ serialised->checked_offsets_up_to = 0;
+ }
+
+ if (serialised->data)
+@@ -515,6 +538,7 @@ g_variant_ensure_serialised (GVariant *value)
+ value->contents.serialised.data = g_bytes_get_data (bytes, NULL);
+ value->contents.serialised.bytes = bytes;
+ value->contents.serialised.ordered_offsets_up_to = G_MAXSIZE;
++ value->contents.serialised.checked_offsets_up_to = G_MAXSIZE;
+ value->state |= STATE_SERIALISED;
+ }
+ }
+@@ -596,6 +620,7 @@ g_variant_new_from_bytes (const GVariantType *type,
+ serialised.data = (guchar *) g_bytes_get_data (bytes, &serialised.size);
+ serialised.depth = 0;
+ serialised.ordered_offsets_up_to = trusted ? G_MAXSIZE : 0;
++ serialised.checked_offsets_up_to = trusted ? G_MAXSIZE : 0;
+
+ if (!g_variant_serialised_check (serialised))
+ {
+@@ -647,6 +672,7 @@ g_variant_new_from_bytes (const GVariantType *type,
+ }
+
+ value->contents.serialised.ordered_offsets_up_to = trusted ? G_MAXSIZE : 0;
++ value->contents.serialised.checked_offsets_up_to = trusted ? G_MAXSIZE : 0;
+
+ g_clear_pointer (&owned_bytes, g_bytes_unref);
+
+@@ -1144,6 +1170,7 @@ g_variant_get_child_value (GVariant *value,
+
+ /* Update the cached ordered_offsets_up_to, since @serialised will be thrown away when this function exits */
+ value->contents.serialised.ordered_offsets_up_to = MAX (value->contents.serialised.ordered_offsets_up_to, serialised.ordered_offsets_up_to);
++ value->contents.serialised.checked_offsets_up_to = MAX (value->contents.serialised.checked_offsets_up_to, serialised.checked_offsets_up_to);
+
+ /* Check whether this would cause nesting too deep. If so, return a fake
+ * child. The only situation we expect this to happen in is with a variant,
+@@ -1171,6 +1198,7 @@ g_variant_get_child_value (GVariant *value,
+ g_bytes_ref (value->contents.serialised.bytes);
+ child->contents.serialised.data = s_child.data;
+ child->contents.serialised.ordered_offsets_up_to = s_child.ordered_offsets_up_to;
++ child->contents.serialised.checked_offsets_up_to = s_child.checked_offsets_up_to;
+
+ return child;
+ }
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index fda59d6843..591522a2e1 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -122,6 +122,8 @@
+ *
+ * @depth has no restrictions; the depth of a top-level serialized #GVariant is
+ * zero, and it increases for each level of nested child.
++ *
++ * @checked_offsets_up_to is always ≥ @ordered_offsets_up_to
+ */
+
+ /* < private >
+@@ -149,6 +151,9 @@ g_variant_serialised_check (GVariantSerialised serialised)
+ !(serialised.size == 0 || serialised.data != NULL))
+ return FALSE;
+
++ if (serialised.ordered_offsets_up_to > serialised.checked_offsets_up_to)
++ return FALSE;
++
+ /* Depending on the native alignment requirements of the machine, the
+ * compiler will insert either 3 or 7 padding bytes after the char.
+ * This will result in the sizeof() the struct being 12 or 16.
+@@ -268,6 +273,7 @@ gvs_fixed_sized_maybe_get_child (GVariantSerialised value,
+ g_variant_type_info_ref (value.type_info);
+ value.depth++;
+ value.ordered_offsets_up_to = 0;
++ value.checked_offsets_up_to = 0;
+
+ return value;
+ }
+@@ -299,7 +305,7 @@ gvs_fixed_sized_maybe_serialise (GVariantSerialised value,
+ {
+ if (n_children)
+ {
+- GVariantSerialised child = { NULL, value.data, value.size, value.depth + 1, 0 };
++ GVariantSerialised child = { NULL, value.data, value.size, value.depth + 1, 0, 0 };
+
+ gvs_filler (&child, children[0]);
+ }
+@@ -322,6 +328,7 @@ gvs_fixed_sized_maybe_is_normal (GVariantSerialised value)
+ value.type_info = g_variant_type_info_element (value.type_info);
+ value.depth++;
+ value.ordered_offsets_up_to = 0;
++ value.checked_offsets_up_to = 0;
+
+ return g_variant_serialised_is_normal (value);
+ }
+@@ -364,6 +371,7 @@ gvs_variable_sized_maybe_get_child (GVariantSerialised value,
+
+ value.depth++;
+ value.ordered_offsets_up_to = 0;
++ value.checked_offsets_up_to = 0;
+
+ return value;
+ }
+@@ -394,7 +402,7 @@ gvs_variable_sized_maybe_serialise (GVariantSerialised value,
+ {
+ if (n_children)
+ {
+- GVariantSerialised child = { NULL, value.data, value.size - 1, value.depth + 1, 0 };
++ GVariantSerialised child = { NULL, value.data, value.size - 1, value.depth + 1, 0, 0 };
+
+ /* write the data for the child. */
+ gvs_filler (&child, children[0]);
+@@ -415,6 +423,7 @@ gvs_variable_sized_maybe_is_normal (GVariantSerialised value)
+ value.size--;
+ value.depth++;
+ value.ordered_offsets_up_to = 0;
++ value.checked_offsets_up_to = 0;
+
+ return g_variant_serialised_is_normal (value);
+ }
+@@ -741,39 +750,46 @@ gvs_variable_sized_array_get_child (GVariantSerialised value,
+
+ /* If the requested @index_ is beyond the set of indices whose framing offsets
+ * have been checked, check the remaining offsets to see whether they’re
+- * normal (in order, no overlapping array elements). */
+- if (index_ > value.ordered_offsets_up_to)
++ * normal (in order, no overlapping array elements).
++ *
++ * Don’t bother checking if the highest known-good offset is lower than the
++ * highest checked offset, as that means there’s an invalid element at that
++ * index, so there’s no need to check further. */
++ if (index_ > value.checked_offsets_up_to &&
++ value.ordered_offsets_up_to == value.checked_offsets_up_to)
+ {
+ switch (offsets.offset_size)
+ {
+ case 1:
+ {
+ value.ordered_offsets_up_to = find_unordered_guint8 (
+- offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ offsets.array, value.checked_offsets_up_to, index_ + 1);
+ break;
+ }
+ case 2:
+ {
+ value.ordered_offsets_up_to = find_unordered_guint16 (
+- offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ offsets.array, value.checked_offsets_up_to, index_ + 1);
+ break;
+ }
+ case 4:
+ {
+ value.ordered_offsets_up_to = find_unordered_guint32 (
+- offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ offsets.array, value.checked_offsets_up_to, index_ + 1);
+ break;
+ }
+ case 8:
+ {
+ value.ordered_offsets_up_to = find_unordered_guint64 (
+- offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ offsets.array, value.checked_offsets_up_to, index_ + 1);
+ break;
+ }
+ default:
+ /* gvs_get_offset_size() only returns maximum 8 */
+ g_assert_not_reached ();
+ }
++
++ value.checked_offsets_up_to = index_;
+ }
+
+ if (index_ > value.ordered_offsets_up_to)
+@@ -918,6 +934,7 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+
+ /* All offsets have now been checked. */
+ value.ordered_offsets_up_to = G_MAXSIZE;
++ value.checked_offsets_up_to = G_MAXSIZE;
+
+ return TRUE;
+ }
+@@ -1042,14 +1059,15 @@ gvs_tuple_get_child (GVariantSerialised value,
+ * all the tuple *elements* here, not just all the framing offsets, since
+ * tuples contain a mix of elements which use framing offsets and ones which
+ * don’t. None of them are allowed to overlap. */
+- if (index_ > value.ordered_offsets_up_to)
++ if (index_ > value.checked_offsets_up_to &&
++ value.ordered_offsets_up_to == value.checked_offsets_up_to)
+ {
+ gsize i, prev_i_end = 0;
+
+- if (value.ordered_offsets_up_to > 0)
+- gvs_tuple_get_member_bounds (value, value.ordered_offsets_up_to - 1, offset_size, NULL, &prev_i_end);
++ if (value.checked_offsets_up_to > 0)
++ gvs_tuple_get_member_bounds (value, value.checked_offsets_up_to - 1, offset_size, NULL, &prev_i_end);
+
+- for (i = value.ordered_offsets_up_to; i <= index_; i++)
++ for (i = value.checked_offsets_up_to; i <= index_; i++)
+ {
+ gsize i_start, i_end;
+
+@@ -1062,6 +1080,7 @@ gvs_tuple_get_child (GVariantSerialised value,
+ }
+
+ value.ordered_offsets_up_to = i - 1;
++ value.checked_offsets_up_to = index_;
+ }
+
+ if (index_ > value.ordered_offsets_up_to)
+@@ -1259,6 +1278,7 @@ gvs_tuple_is_normal (GVariantSerialised value)
+
+ /* All element bounds have been checked above. */
+ value.ordered_offsets_up_to = G_MAXSIZE;
++ value.checked_offsets_up_to = G_MAXSIZE;
+
+ {
+ gsize fixed_size;
+diff --git a/glib/gvariant-serialiser.h b/glib/gvariant-serialiser.h
+index 661bd8fd1b..eb74fe7804 100644
+--- a/glib/gvariant-serialiser.h
++++ b/glib/gvariant-serialiser.h
+@@ -43,6 +43,15 @@ typedef struct
+ * Even when dealing with tuples, @ordered_offsets_up_to is an element index,
+ * rather than an index into the frame offsets. */
+ gsize ordered_offsets_up_to;
++
++ /* Similar to @ordered_offsets_up_to. This gives the index of the child element
++ * whose frame offset is the highest in the offset table which has been
++ * checked so far.
++ *
++ * This is always ≥ @ordered_offsets_up_to. It is always an element index.
++ *
++ * See documentation in gvariant-core.c for `struct GVariant` for details. */
++ gsize checked_offsets_up_to;
+ } GVariantSerialised;
+
+ /* deserialization */
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index 16af7a5678..48e49490ba 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -6011,6 +6011,7 @@ g_variant_byteswap (GVariant *value)
+ serialised.data = g_malloc (serialised.size);
+ serialised.depth = g_variant_get_depth (trusted);
+ serialised.ordered_offsets_up_to = G_MAXSIZE; /* operating on the normal form */
++ serialised.checked_offsets_up_to = G_MAXSIZE;
+ g_variant_store (trusted, serialised.data);
+ g_variant_unref (trusted);
+
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 0cb69d4b70..941e58f1bc 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -1284,6 +1284,7 @@ random_instance_filler (GVariantSerialised *serialised,
+
+ serialised->depth = 0;
+ serialised->ordered_offsets_up_to = 0;
++ serialised->checked_offsets_up_to = 0;
+
+ g_assert_true (serialised->type_info == instance->type_info);
+ g_assert_cmpuint (serialised->size, ==, instance->size);
+@@ -1451,6 +1452,7 @@ test_maybe (void)
+ serialised.size = needed_size;
+ serialised.depth = 0;
+ serialised.ordered_offsets_up_to = 0;
++ serialised.checked_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised,
+ random_instance_filler,
+@@ -1575,6 +1577,7 @@ test_array (void)
+ serialised.size = needed_size;
+ serialised.depth = 0;
+ serialised.ordered_offsets_up_to = 0;
++ serialised.checked_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised, random_instance_filler,
+ (gpointer *) instances, n_children);
+@@ -1740,6 +1743,7 @@ test_tuple (void)
+ serialised.size = needed_size;
+ serialised.depth = 0;
+ serialised.ordered_offsets_up_to = 0;
++ serialised.checked_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised, random_instance_filler,
+ (gpointer *) instances, n_children);
+@@ -1837,6 +1841,7 @@ test_variant (void)
+ serialised.size = needed_size;
+ serialised.depth = 0;
+ serialised.ordered_offsets_up_to = 0;
++ serialised.checked_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised, random_instance_filler,
+ (gpointer *) &instance, 1);
+--
+GitLab
+
+
+From 8c1a7815e7e6695c120cdedff48395c1222af6d1 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <withnall@endlessm.com>
+Date: Fri, 12 Jun 2020 18:01:13 +0100
+Subject: [PATCH 09/18] tests: Add another test for overlapping offsets in
+ GVariant
+
+Signed-off-by: Philip Withnall <withnall@endlessm.com>
+
+Helps: #2121
+---
+ glib/tests/gvariant.c | 34 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 34 insertions(+)
+
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 941e58f1bc..867cb5b9b4 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -5119,6 +5119,38 @@ test_recursion_limits_array_in_variant (void)
+ g_variant_unref (wrapper_variant);
+ }
+
++/* Test that a nested array with invalid values in its offset table (which point
++ * from the inner to the outer array) is normalised successfully without
++ * looping infinitely. */
++static void
++test_normal_checking_array_offsets_overlapped (void)
++{
++ const guint8 data[] = {
++ 0x01, 0x00,
++ };
++ gsize size = sizeof (data);
++ GVariant *variant = NULL;
++ GVariant *normal_variant = NULL;
++ GVariant *expected_variant = NULL;
++
++ variant = g_variant_new_from_data (G_VARIANT_TYPE ("aay"), data, size,
++ FALSE, NULL, NULL);
++ g_assert_nonnull (variant);
++
++ normal_variant = g_variant_get_normal_form (variant);
++ g_assert_nonnull (normal_variant);
++
++ expected_variant = g_variant_new_parsed ("[@ay [], []]");
++ g_assert_cmpvariant (normal_variant, expected_variant);
++
++ g_assert_cmpmem (g_variant_get_data (normal_variant), g_variant_get_size (normal_variant),
++ g_variant_get_data (expected_variant), g_variant_get_size (expected_variant));
++
++ g_variant_unref (expected_variant);
++ g_variant_unref (normal_variant);
++ g_variant_unref (variant);
++}
++
+ /* Test that an array with invalidly large values in its offset table is
+ * normalised successfully without looping infinitely. */
+ static void
+@@ -5520,6 +5552,8 @@ main (int argc, char **argv)
+
+ g_test_add_func ("/gvariant/normal-checking/tuples",
+ test_normal_checking_tuples);
++ g_test_add_func ("/gvariant/normal-checking/array-offsets/overlapped",
++ test_normal_checking_array_offsets_overlapped);
+ g_test_add_func ("/gvariant/normal-checking/array-offsets",
+ test_normal_checking_array_offsets);
+ g_test_add_func ("/gvariant/normal-checking/array-offsets2",
+--
+GitLab
+
+
+From 019505a7ccc32d0afa06e104dc0ac2e63e6f7189 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Mon, 24 Oct 2022 16:43:23 +0100
+Subject: [PATCH 10/18] tests: Disable some random instance tests of GVariants
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Building a `GVariant` using entirely random data may result in a
+non-normally-formed `GVariant`. It’s always possible to read these
+`GVariant`s, but the API might return default values for some or all of
+their components.
+
+In particular, this can easily happen when randomly generating the
+offset tables for non-fixed-width container types.
+
+If it does happen, bytewise comparison of the parsed `GVariant` with the
+original bytes will not always match. So skip those checks.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2121
+---
+ glib/tests/gvariant.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 867cb5b9b4..9739993e66 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -1231,6 +1231,7 @@ random_instance_assert (RandomInstance *instance,
+ GRand *rand;
+ gsize i;
+
++ g_assert_true (size == 0 || buffer != NULL);
+ g_assert_cmpint ((gsize) buffer & ALIGN_BITS & instance->alignment, ==, 0);
+ g_assert_cmpint (size, ==, instance->size);
+
+@@ -1457,10 +1458,13 @@ test_maybe (void)
+ g_variant_serialiser_serialise (serialised,
+ random_instance_filler,
+ (gpointer *) &instance, 1);
++
+ child = g_variant_serialised_get_child (serialised, 0);
+ g_assert_true (child.type_info == instance->type_info);
+- random_instance_assert (instance, child.data, child.size);
++ if (child.data != NULL) /* could be NULL if element is non-normal */
++ random_instance_assert (instance, child.data, child.size);
+ g_variant_type_info_unref (child.type_info);
++
+ flavoured_free (serialised.data, flavour);
+ }
+ }
+@@ -1593,7 +1597,8 @@ test_array (void)
+
+ child = g_variant_serialised_get_child (serialised, i);
+ g_assert_true (child.type_info == instances[i]->type_info);
+- random_instance_assert (instances[i], child.data, child.size);
++ if (child.data != NULL) /* could be NULL if element is non-normal */
++ random_instance_assert (instances[i], child.data, child.size);
+ g_variant_type_info_unref (child.type_info);
+ }
+
+@@ -1759,7 +1764,8 @@ test_tuple (void)
+
+ child = g_variant_serialised_get_child (serialised, i);
+ g_assert_true (child.type_info == instances[i]->type_info);
+- random_instance_assert (instances[i], child.data, child.size);
++ if (child.data != NULL) /* could be NULL if element is non-normal */
++ random_instance_assert (instances[i], child.data, child.size);
+ g_variant_type_info_unref (child.type_info);
+ }
+
+--
+GitLab
+
+
+From 9d2a142807806212a23436d0332b0209733810f2 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Mon, 24 Oct 2022 18:14:57 +0100
+Subject: [PATCH 11/18] gvariant: Clarify the docs for
+ g_variant_get_normal_form()
+
+Document how non-normal parts of the `GVariant` are handled.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+---
+ glib/gvariant.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index 48e49490ba..e92564eb64 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -5936,7 +5936,9 @@ g_variant_deep_copy (GVariant *value)
+ * marked as trusted and a new reference to it is returned.
+ *
+ * If @value is found not to be in normal form then a new trusted
+- * #GVariant is created with the same value as @value.
++ * #GVariant is created with the same value as @value. The non-normal parts of
++ * @value will be replaced with default values which are guaranteed to be in
++ * normal form.
+ *
+ * It makes sense to call this function if you've received #GVariant
+ * data from untrusted sources and you want to ensure your serialized
+--
+GitLab
+
+
+From b0ccb1616688290088e49dea2dc0d7fe723136e4 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Mon, 24 Oct 2022 18:43:55 +0100
+Subject: [PATCH 12/18] gvariant: Port g_variant_deep_copy() to count its
+ iterations directly
+
+This is equivalent to what `GVariantIter` does, but it means that
+`g_variant_deep_copy()` is making its own `g_variant_get_child_value()`
+calls.
+
+This will be useful in an upcoming commit, where those child values will
+be inspected a little more deeply.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2121
+---
+ glib/gvariant.c | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index e92564eb64..82a921a89e 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -5863,14 +5863,13 @@ g_variant_deep_copy (GVariant *value)
+ case G_VARIANT_CLASS_VARIANT:
+ {
+ GVariantBuilder builder;
+- GVariantIter iter;
+- GVariant *child;
++ gsize i, n_children;
+
+ g_variant_builder_init (&builder, g_variant_get_type (value));
+- g_variant_iter_init (&iter, value);
+
+- while ((child = g_variant_iter_next_value (&iter)))
++ for (i = 0, n_children = g_variant_n_children (value); i < n_children; i++)
+ {
++ GVariant *child = g_variant_get_child_value (value, i);
+ g_variant_builder_add_value (&builder, g_variant_deep_copy (child));
+ g_variant_unref (child);
+ }
+--
+GitLab
+
+
+From 1770e255ae6cc3f0bf5312322432bbc6524a3632 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Tue, 25 Oct 2022 13:03:22 +0100
+Subject: [PATCH 13/18] gvariant: Add internal
+ g_variant_maybe_get_child_value()
+
+This will be used in a following commit.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2540
+---
+ glib/gvariant-core.c | 68 ++++++++++++++++++++++++++++++++++++++++++++
+ glib/gvariant-core.h | 3 ++
+ 2 files changed, 71 insertions(+)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index e61242f763..3f4ca0e6dc 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -1204,6 +1204,74 @@ g_variant_get_child_value (GVariant *value,
+ }
+ }
+
++/**
++ * g_variant_maybe_get_child_value:
++ * @value: a container #GVariant
++ * @index_: the index of the child to fetch
++ *
++ * Reads a child item out of a container #GVariant instance, if it is in normal
++ * form. If it is not in normal form, return %NULL.
++ *
++ * This function behaves the same as g_variant_get_child_value(), except that it
++ * returns %NULL if the child is not in normal form. g_variant_get_child_value()
++ * would instead return a new default value of the correct type.
++ *
++ * This is intended to be used internally to avoid unnecessary #GVariant
++ * allocations.
++ *
++ * The returned value is never floating. You should free it with
++ * g_variant_unref() when you're done with it.
++ *
++ * This function is O(1).
++ *
++ * Returns: (transfer full): the child at the specified index
++ *
++ * Since: 2.74
++ */
++GVariant *
++g_variant_maybe_get_child_value (GVariant *value,
++ gsize index_)
++{
++ g_return_val_if_fail (index_ < g_variant_n_children (value), NULL);
++ g_return_val_if_fail (value->depth < G_MAXSIZE, NULL);
++
++ if (~g_atomic_int_get (&value->state) & STATE_SERIALISED)
++ {
++ g_variant_lock (value);
++
++ if (~value->state & STATE_SERIALISED)
++ {
++ GVariant *child;
++
++ child = g_variant_ref (value->contents.tree.children[index_]);
++ g_variant_unlock (value);
++
++ return child;
++ }
++
++ g_variant_unlock (value);
++ }
++
++ {
++ GVariantSerialised serialised = g_variant_to_serialised (value);
++ GVariantSerialised s_child;
++
++ /* get the serializer to extract the serialized data for the child
++ * from the serialized data for the container
++ */
++ s_child = g_variant_serialised_get_child (serialised, index_);
++
++ if (!(value->state & STATE_TRUSTED) && s_child.data == NULL)
++ {
++ g_variant_type_info_unref (s_child.type_info);
++ return NULL;
++ }
++
++ g_variant_type_info_unref (s_child.type_info);
++ return g_variant_get_child_value (value, index_);
++ }
++}
++
+ /**
+ * g_variant_store:
+ * @value: the #GVariant to store
+diff --git a/glib/gvariant-core.h b/glib/gvariant-core.h
+index a1c34739a3..fb5f4bff45 100644
+--- a/glib/gvariant-core.h
++++ b/glib/gvariant-core.h
+@@ -38,4 +38,7 @@ GVariantTypeInfo * g_variant_get_type_info (GVarian
+
+ gsize g_variant_get_depth (GVariant *value);
+
++GVariant * g_variant_maybe_get_child_value (GVariant *value,
++ gsize index_);
++
+ #endif /* __G_VARIANT_CORE_H__ */
+--
+GitLab
+
+
+From 82fc15af4c84a2645343c51b18ab3528c51790ab Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Tue, 25 Oct 2022 13:03:45 +0100
+Subject: [PATCH 14/18] gvariant: Cut allocs of default values for children of
+ non-normal arrays
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This improves a slow case in `g_variant_get_normal_form()` where
+allocating many identical default values for the children of a
+variable-sized array which has a malformed offset table would take a lot
+of time.
+
+The fix is to make all child values after the first invalid one be
+references to the default value emitted for the first invalid one,
+rather than identical new `GVariant`s.
+
+In particular, this fixes a case where an attacker could create an array
+of length L of very large tuples of size T each, corrupt the offset table
+so they don’t have to specify the array content, and then induce
+`g_variant_get_normal_form()` into allocating L×T default values from an
+input which is significantly smaller than L×T in length.
+
+A pre-existing workaround for this issue is for code to call
+`g_variant_is_normal_form()` before calling
+`g_variant_get_normal_form()`, and to skip the latter call if the former
+returns false. This commit improves the behaviour in the case that
+`g_variant_get_normal_form()` is called anyway.
+
+This fix changes the time to run the `fuzz_variant_binary` test on the
+testcase from oss-fuzz#19777 from >60s (before being terminated) with
+2.3GB of memory usage and 580k page faults; to 32s, 8.3MB of memory
+usage and 1500 page faults (as measured by `time -v`).
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Fixes: #2540
+oss-fuzz#19777
+---
+ glib/gvariant.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 65 insertions(+), 1 deletion(-)
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index 82a921a89e..bb6be363e0 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -5857,7 +5857,6 @@ g_variant_deep_copy (GVariant *value)
+ switch (g_variant_classify (value))
+ {
+ case G_VARIANT_CLASS_MAYBE:
+- case G_VARIANT_CLASS_ARRAY:
+ case G_VARIANT_CLASS_TUPLE:
+ case G_VARIANT_CLASS_DICT_ENTRY:
+ case G_VARIANT_CLASS_VARIANT:
+@@ -5877,6 +5876,71 @@ g_variant_deep_copy (GVariant *value)
+ return g_variant_builder_end (&builder);
+ }
+
++ case G_VARIANT_CLASS_ARRAY:
++ {
++ GVariantBuilder builder;
++ gsize i, n_children;
++ GVariant *first_invalid_child_deep_copy = NULL;
++
++ /* Arrays are in theory treated the same as maybes, tuples, dict entries
++ * and variants, and could be another case in the above block of code.
++ *
++ * However, they have the property that when dealing with non-normal
++ * data (which is the only time g_variant_deep_copy() is currently
++ * called) in a variable-sized array, the code above can easily end up
++ * creating many default child values in order to return an array which
++ * is of the right length and type, but without containing non-normal
++ * data. This can happen if the offset table for the array is malformed.
++ *
++ * In this case, the code above would end up allocating the same default
++ * value for each one of the child indexes beyond the first malformed
++ * entry in the offset table. This can end up being a lot of identical
++ * allocations of default values, particularly if the non-normal array
++ * is crafted maliciously.
++ *
++ * Avoid that problem by returning a new reference to the same default
++ * value for every child after the first invalid one. This results in
++ * returning an equivalent array, in normal form and trusted — but with
++ * significantly fewer memory allocations.
++ *
++ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2540 */
++
++ g_variant_builder_init (&builder, g_variant_get_type (value));
++
++ for (i = 0, n_children = g_variant_n_children (value); i < n_children; i++)
++ {
++ /* Try maybe_get_child_value() first; if it returns NULL, this child
++ * is non-normal. get_child_value() would have constructed and
++ * returned a default value in that case. */
++ GVariant *child = g_variant_maybe_get_child_value (value, i);
++
++ if (child != NULL)
++ {
++ /* Non-normal children may not always be contiguous, as they may
++ * be non-normal for reasons other than invalid offset table
++ * entries. As they are all the same type, they will all have
++ * the same default value though, so keep that around. */
++ g_variant_builder_add_value (&builder, g_variant_deep_copy (child));
++ }
++ else if (child == NULL && first_invalid_child_deep_copy != NULL)
++ {
++ g_variant_builder_add_value (&builder, first_invalid_child_deep_copy);
++ }
++ else if (child == NULL)
++ {
++ child = g_variant_get_child_value (value, i);
++ first_invalid_child_deep_copy = g_variant_ref_sink (g_variant_deep_copy (child));
++ g_variant_builder_add_value (&builder, first_invalid_child_deep_copy);
++ }
++
++ g_clear_pointer (&child, g_variant_unref);
++ }
++
++ g_clear_pointer (&first_invalid_child_deep_copy, g_variant_unref);
++
++ return g_variant_builder_end (&builder);
++ }
++
+ case G_VARIANT_CLASS_BOOLEAN:
+ return g_variant_new_boolean (g_variant_get_boolean (value));
+
+--
+GitLab
+
+
+From 935f1c200789c76ad5b51b1f403f611e3cc75318 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Tue, 25 Oct 2022 18:03:56 +0100
+Subject: [PATCH 15/18] gvariant: Fix a leak of a GVariantTypeInfo on an error
+ handling path
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+---
+ glib/gvariant-core.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index 3f4ca0e6dc..75d23f39af 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -1183,6 +1183,7 @@ g_variant_get_child_value (GVariant *value,
+ G_VARIANT_MAX_RECURSION_DEPTH - value->depth)
+ {
+ g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_VARIANT));
++ g_variant_type_info_unref (s_child.type_info);
+ return g_variant_new_tuple (NULL, 0);
+ }
+
+--
+GitLab
+
+
+From f1dfc5d0c5c0486b5fccb2ceddf1db1162c7033c Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Thu, 27 Oct 2022 12:00:04 +0100
+Subject: [PATCH 16/18] gvariant-serialiser: Check offset table entry size is
+ minimal
+
+The entries in an offset table (which is used for variable sized arrays
+and tuples containing variable sized members) are sized so that they can
+address every byte in the overall variant.
+
+The specification requires that for a variant to be in normal form, its
+offset table entries must be the minimum width such that they can
+address every byte in the variant.
+
+That minimality requirement was not checked in
+`g_variant_is_normal_form()`, leading to two different byte arrays being
+interpreted as the normal form of a given variant tree. That kind of
+confusion could potentially be exploited, and is certainly a bug.
+
+Fix it by adding the necessary checks on offset table entry width, and
+unit tests.
+
+Spotted by William Manley.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Fixes: #2794
+---
+ glib/gvariant-serialiser.c | 19 +++-
+ glib/tests/gvariant.c | 176 +++++++++++++++++++++++++++++++++++++
+ 2 files changed, 194 insertions(+), 1 deletion(-)
+
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index 591522a2e1..25c85b30b1 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -696,6 +696,10 @@ gvs_variable_sized_array_get_frame_offsets (GVariantSerialised value)
+ out.data_size = last_end;
+ out.array = value.data + last_end;
+ out.length = offsets_array_size / out.offset_size;
++
++ if (out.length > 0 && gvs_calculate_total_size (last_end, out.length) != value.size)
++ return out; /* offset size not minimal */
++
+ out.is_normal = TRUE;
+
+ return out;
+@@ -1203,6 +1207,7 @@ gvs_tuple_is_normal (GVariantSerialised value)
+ gsize length;
+ gsize offset;
+ gsize i;
++ gsize offset_table_size;
+
+ /* as per the comment in gvs_tuple_get_child() */
+ if G_UNLIKELY (value.data == NULL && value.size != 0)
+@@ -1307,7 +1312,19 @@ gvs_tuple_is_normal (GVariantSerialised value)
+ }
+ }
+
+- return offset_ptr == offset;
++ /* @offset_ptr has been counting backwards from the end of the variant, to
++ * find the beginning of the offset table. @offset has been counting forwards
++ * from the beginning of the variant to find the end of the data. They should
++ * have met in the middle. */
++ if (offset_ptr != offset)
++ return FALSE;
++
++ offset_table_size = value.size - offset_ptr;
++ if (value.size > 0 &&
++ gvs_calculate_total_size (offset, offset_table_size / offset_size) != value.size)
++ return FALSE; /* offset size not minimal */
++
++ return TRUE;
+ }
+
+ /* Variants {{{2
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 9739993e66..339edc4e04 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -5222,6 +5222,86 @@ test_normal_checking_array_offsets2 (void)
+ g_variant_unref (variant);
+ }
+
++/* Test that an otherwise-valid serialised GVariant is considered non-normal if
++ * its offset table entries are too wide.
++ *
++ * See §2.3.6 (Framing Offsets) of the GVariant specification. */
++static void
++test_normal_checking_array_offsets_minimal_sized (void)
++{
++ GVariantBuilder builder;
++ gsize i;
++ GVariant *aay_constructed = NULL;
++ const guint8 *data = NULL;
++ guint8 *data_owned = NULL;
++ GVariant *aay_deserialised = NULL;
++ GVariant *aay_normalised = NULL;
++
++ /* Construct an array of type aay, consisting of 128 elements which are each
++ * an empty array, i.e. `[[] * 128]`. This is chosen because the inner
++ * elements are variable sized (making the outer array variable sized, so it
++ * must have an offset table), but they are also zero-sized when serialised.
++ * So the serialised representation of @aay_constructed consists entirely of
++ * its offset table, which is entirely zeroes.
++ *
++ * The array is chosen to be 128 elements long because that means offset
++ * table entries which are 1 byte long. If the elements in the array were
++ * non-zero-sized (to the extent that the overall array is ≥256 bytes long),
++ * the offset table entries would end up being 2 bytes long. */
++ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aay"));
++
++ for (i = 0; i < 128; i++)
++ g_variant_builder_add_value (&builder, g_variant_new_array (G_VARIANT_TYPE_BYTE, NULL, 0));
++
++ aay_constructed = g_variant_builder_end (&builder);
++
++ /* Verify that the constructed array is in normal form, and its serialised
++ * form is `b'\0' * 128`. */
++ g_assert_true (g_variant_is_normal_form (aay_constructed));
++ g_assert_cmpuint (g_variant_n_children (aay_constructed), ==, 128);
++ g_assert_cmpuint (g_variant_get_size (aay_constructed), ==, 128);
++
++ data = g_variant_get_data (aay_constructed);
++ for (i = 0; i < g_variant_get_size (aay_constructed); i++)
++ g_assert_cmpuint (data[i], ==, 0);
++
++ /* Construct a serialised `aay` GVariant which is `b'\0' * 256`. This has to
++ * be a non-normal form of `[[] * 128]`, with 2-byte-long offset table
++ * entries, because each offset table entry has to be able to reference all of
++ * the byte boundaries in the container. All the entries in the offset table
++ * are zero, so all the elements of the array are zero-sized. */
++ data = data_owned = g_malloc0 (256);
++ aay_deserialised = g_variant_new_from_data (G_VARIANT_TYPE ("aay"),
++ data,
++ 256,
++ FALSE,
++ g_free,
++ g_steal_pointer (&data_owned));
++
++ g_assert_false (g_variant_is_normal_form (aay_deserialised));
++ g_assert_cmpuint (g_variant_n_children (aay_deserialised), ==, 128);
++ g_assert_cmpuint (g_variant_get_size (aay_deserialised), ==, 256);
++
++ data = g_variant_get_data (aay_deserialised);
++ for (i = 0; i < g_variant_get_size (aay_deserialised); i++)
++ g_assert_cmpuint (data[i], ==, 0);
++
++ /* Get its normal form. That should change the serialised size. */
++ aay_normalised = g_variant_get_normal_form (aay_deserialised);
++
++ g_assert_true (g_variant_is_normal_form (aay_normalised));
++ g_assert_cmpuint (g_variant_n_children (aay_normalised), ==, 128);
++ g_assert_cmpuint (g_variant_get_size (aay_normalised), ==, 128);
++
++ data = g_variant_get_data (aay_normalised);
++ for (i = 0; i < g_variant_get_size (aay_normalised); i++)
++ g_assert_cmpuint (data[i], ==, 0);
++
++ g_variant_unref (aay_normalised);
++ g_variant_unref (aay_deserialised);
++ g_variant_unref (aay_constructed);
++}
++
+ /* Test that a tuple with invalidly large values in its offset table is
+ * normalised successfully without looping infinitely. */
+ static void
+@@ -5416,6 +5496,98 @@ test_normal_checking_tuple_offsets4 (void)
+ g_variant_unref (variant);
+ }
+
++/* Test that an otherwise-valid serialised GVariant is considered non-normal if
++ * its offset table entries are too wide.
++ *
++ * See §2.3.6 (Framing Offsets) of the GVariant specification. */
++static void
++test_normal_checking_tuple_offsets_minimal_sized (void)
++{
++ GString *type_string = NULL;
++ GVariantBuilder builder;
++ gsize i;
++ GVariant *ray_constructed = NULL;
++ const guint8 *data = NULL;
++ guint8 *data_owned = NULL;
++ GVariant *ray_deserialised = NULL;
++ GVariant *ray_normalised = NULL;
++
++ /* Construct a tuple of type (ay…ay), consisting of 129 members which are each
++ * an empty array, i.e. `([] * 129)`. This is chosen because the inner
++ * members are variable sized, so the outer tuple must have an offset table,
++ * but they are also zero-sized when serialised. So the serialised
++ * representation of @ray_constructed consists entirely of its offset table,
++ * which is entirely zeroes.
++ *
++ * The tuple is chosen to be 129 members long because that means it has 128
++ * offset table entries which are 1 byte long each. If the members in the
++ * tuple were non-zero-sized (to the extent that the overall tuple is ≥256
++ * bytes long), the offset table entries would end up being 2 bytes long.
++ *
++ * 129 members are used unlike 128 array elements in
++ * test_normal_checking_array_offsets_minimal_sized(), because the last member
++ * in a tuple never needs an offset table entry. */
++ type_string = g_string_new ("");
++ g_string_append_c (type_string, '(');
++ for (i = 0; i < 129; i++)
++ g_string_append (type_string, "ay");
++ g_string_append_c (type_string, ')');
++
++ g_variant_builder_init (&builder, G_VARIANT_TYPE (type_string->str));
++
++ for (i = 0; i < 129; i++)
++ g_variant_builder_add_value (&builder, g_variant_new_array (G_VARIANT_TYPE_BYTE, NULL, 0));
++
++ ray_constructed = g_variant_builder_end (&builder);
++
++ /* Verify that the constructed tuple is in normal form, and its serialised
++ * form is `b'\0' * 128`. */
++ g_assert_true (g_variant_is_normal_form (ray_constructed));
++ g_assert_cmpuint (g_variant_n_children (ray_constructed), ==, 129);
++ g_assert_cmpuint (g_variant_get_size (ray_constructed), ==, 128);
++
++ data = g_variant_get_data (ray_constructed);
++ for (i = 0; i < g_variant_get_size (ray_constructed); i++)
++ g_assert_cmpuint (data[i], ==, 0);
++
++ /* Construct a serialised `(ay…ay)` GVariant which is `b'\0' * 256`. This has
++ * to be a non-normal form of `([] * 129)`, with 2-byte-long offset table
++ * entries, because each offset table entry has to be able to reference all of
++ * the byte boundaries in the container. All the entries in the offset table
++ * are zero, so all the members of the tuple are zero-sized. */
++ data = data_owned = g_malloc0 (256);
++ ray_deserialised = g_variant_new_from_data (G_VARIANT_TYPE (type_string->str),
++ data,
++ 256,
++ FALSE,
++ g_free,
++ g_steal_pointer (&data_owned));
++
++ g_assert_false (g_variant_is_normal_form (ray_deserialised));
++ g_assert_cmpuint (g_variant_n_children (ray_deserialised), ==, 129);
++ g_assert_cmpuint (g_variant_get_size (ray_deserialised), ==, 256);
++
++ data = g_variant_get_data (ray_deserialised);
++ for (i = 0; i < g_variant_get_size (ray_deserialised); i++)
++ g_assert_cmpuint (data[i], ==, 0);
++
++ /* Get its normal form. That should change the serialised size. */
++ ray_normalised = g_variant_get_normal_form (ray_deserialised);
++
++ g_assert_true (g_variant_is_normal_form (ray_normalised));
++ g_assert_cmpuint (g_variant_n_children (ray_normalised), ==, 129);
++ g_assert_cmpuint (g_variant_get_size (ray_normalised), ==, 128);
++
++ data = g_variant_get_data (ray_normalised);
++ for (i = 0; i < g_variant_get_size (ray_normalised); i++)
++ g_assert_cmpuint (data[i], ==, 0);
++
++ g_variant_unref (ray_normalised);
++ g_variant_unref (ray_deserialised);
++ g_variant_unref (ray_constructed);
++ g_string_free (type_string, TRUE);
++}
++
+ /* Test that an empty object path is normalised successfully to the base object
+ * path, ‘/’. */
+ static void
+@@ -5564,6 +5736,8 @@ main (int argc, char **argv)
+ test_normal_checking_array_offsets);
+ g_test_add_func ("/gvariant/normal-checking/array-offsets2",
+ test_normal_checking_array_offsets2);
++ g_test_add_func ("/gvariant/normal-checking/array-offsets/minimal-sized",
++ test_normal_checking_array_offsets_minimal_sized);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets",
+ test_normal_checking_tuple_offsets);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets2",
+@@ -5572,6 +5746,8 @@ main (int argc, char **argv)
+ test_normal_checking_tuple_offsets3);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets4",
+ test_normal_checking_tuple_offsets4);
++ g_test_add_func ("/gvariant/normal-checking/tuple-offsets/minimal-sized",
++ test_normal_checking_tuple_offsets_minimal_sized);
+ g_test_add_func ("/gvariant/normal-checking/empty-object-path",
+ test_normal_checking_empty_object_path);
+
+--
+GitLab
+
+
+From 781f05a22ef11d8a2177b4e9078978decec36dd0 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Thu, 27 Oct 2022 16:13:54 +0100
+Subject: [PATCH 17/18] gvariant: Fix g_variant_byteswap() returning non-normal
+ data sometimes
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+If `g_variant_byteswap()` was called on a non-normal variant of a type
+which doesn’t need byteswapping, it would return a non-normal output.
+
+That contradicts the documentation, which says that the return value is
+always in normal form.
+
+Fix the code so it matches the documentation.
+
+Includes a unit test.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2797
+---
+ glib/gvariant.c | 8 +++++---
+ glib/tests/gvariant.c | 24 ++++++++++++++++++++++++
+ 2 files changed, 29 insertions(+), 3 deletions(-)
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index bb6be363e0..b3495d5cab 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -6083,14 +6083,16 @@ g_variant_byteswap (GVariant *value)
+ g_variant_serialised_byteswap (serialised);
+
+ bytes = g_bytes_new_take (serialised.data, serialised.size);
+- new = g_variant_new_from_bytes (g_variant_get_type (value), bytes, TRUE);
++ new = g_variant_ref_sink (g_variant_new_from_bytes (g_variant_get_type (value), bytes, TRUE));
+ g_bytes_unref (bytes);
+ }
+ else
+ /* contains no multi-byte data */
+- new = value;
++ new = g_variant_get_normal_form (value);
+
+- return g_variant_ref_sink (new);
++ g_assert (g_variant_is_trusted (new));
++
++ return g_steal_pointer (&new);
+ }
+
+ /**
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 339edc4e04..bea16b5e58 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -3826,6 +3826,29 @@ test_gv_byteswap (void)
+ g_free (string);
+ }
+
++static void
++test_gv_byteswap_non_normal_non_aligned (void)
++{
++ const guint8 data[] = { 0x02 };
++ GVariant *v = NULL;
++ GVariant *v_byteswapped = NULL;
++
++ g_test_summary ("Test that calling g_variant_byteswap() on a variant which "
++ "is in non-normal form and doesn’t need byteswapping returns "
++ "the same variant in normal form.");
++
++ v = g_variant_new_from_data (G_VARIANT_TYPE_BOOLEAN, data, sizeof (data), FALSE, NULL, NULL);
++ g_assert_false (g_variant_is_normal_form (v));
++
++ v_byteswapped = g_variant_byteswap (v);
++ g_assert_true (g_variant_is_normal_form (v_byteswapped));
++
++ g_assert_cmpvariant (v, v_byteswapped);
++
++ g_variant_unref (v);
++ g_variant_unref (v_byteswapped);
++}
++
+ static void
+ test_parser (void)
+ {
+@@ -5699,6 +5722,7 @@ main (int argc, char **argv)
+ g_test_add_func ("/gvariant/builder-memory", test_builder_memory);
+ g_test_add_func ("/gvariant/hashing", test_hashing);
+ g_test_add_func ("/gvariant/byteswap", test_gv_byteswap);
++ g_test_add_func ("/gvariant/byteswap/non-normal-non-aligned", test_gv_byteswap_non_normal_non_aligned);
+ g_test_add_func ("/gvariant/parser", test_parses);
+ g_test_add_func ("/gvariant/parser/integer-bounds", test_parser_integer_bounds);
+ g_test_add_func ("/gvariant/parser/recursion", test_parser_recursion);
+--
+GitLab
+
+
+From 7d7efce1d9c379fdd7d2ff58caea88f8806fdd2e Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Thu, 27 Oct 2022 22:53:13 +0100
+Subject: [PATCH 18/18] gvariant: Allow g_variant_byteswap() to operate on
+ tree-form variants
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This avoids needing to always serialise a variant before byteswapping it.
+With variants in non-normal forms, serialisation can result in a large
+increase in size of the variant, and a lot of allocations for leaf
+`GVariant`s. This can lead to a denial of service attack.
+
+Avoid that by changing byteswapping so that it happens on the tree form
+of the variant if the input is in non-normal form. If the input is in
+normal form (either serialised or in tree form), continue using the
+existing code as byteswapping an already-serialised normal variant is
+about 3× faster than byteswapping on the equivalent tree form.
+
+The existing unit tests cover byteswapping well, but need some
+adaptation so that they operate on tree form variants too.
+
+I considered dropping the serialised byteswapping code and doing all
+byteswapping on tree-form variants, as that would make maintenance
+simpler (avoiding having two parallel implementations of byteswapping).
+However, most inputs to `g_variant_byteswap()` are likely to be
+serialised variants (coming from a byte array of input from some foreign
+source) and most of them are going to be in normal form (as corruption
+and malicious action are rare). So getting rid of the serialised
+byteswapping code would impose quite a performance penalty on the common
+case.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Fixes: #2797
+---
+ glib/gvariant.c | 87 ++++++++++++++++++++++++++++++++-----------
+ glib/tests/gvariant.c | 57 ++++++++++++++++++++++++----
+ 2 files changed, 115 insertions(+), 29 deletions(-)
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index b3495d5cab..95904bd382 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -5852,7 +5852,8 @@ g_variant_iter_loop (GVariantIter *iter,
+
+ /* Serialized data {{{1 */
+ static GVariant *
+-g_variant_deep_copy (GVariant *value)
++g_variant_deep_copy (GVariant *value,
++ gboolean byteswap)
+ {
+ switch (g_variant_classify (value))
+ {
+@@ -5869,7 +5870,7 @@ g_variant_deep_copy (GVariant *value)
+ for (i = 0, n_children = g_variant_n_children (value); i < n_children; i++)
+ {
+ GVariant *child = g_variant_get_child_value (value, i);
+- g_variant_builder_add_value (&builder, g_variant_deep_copy (child));
++ g_variant_builder_add_value (&builder, g_variant_deep_copy (child, byteswap));
+ g_variant_unref (child);
+ }
+
+@@ -5920,7 +5921,7 @@ g_variant_deep_copy (GVariant *value)
+ * be non-normal for reasons other than invalid offset table
+ * entries. As they are all the same type, they will all have
+ * the same default value though, so keep that around. */
+- g_variant_builder_add_value (&builder, g_variant_deep_copy (child));
++ g_variant_builder_add_value (&builder, g_variant_deep_copy (child, byteswap));
+ }
+ else if (child == NULL && first_invalid_child_deep_copy != NULL)
+ {
+@@ -5929,7 +5930,7 @@ g_variant_deep_copy (GVariant *value)
+ else if (child == NULL)
+ {
+ child = g_variant_get_child_value (value, i);
+- first_invalid_child_deep_copy = g_variant_ref_sink (g_variant_deep_copy (child));
++ first_invalid_child_deep_copy = g_variant_ref_sink (g_variant_deep_copy (child, byteswap));
+ g_variant_builder_add_value (&builder, first_invalid_child_deep_copy);
+ }
+
+@@ -5948,28 +5949,63 @@ g_variant_deep_copy (GVariant *value)
+ return g_variant_new_byte (g_variant_get_byte (value));
+
+ case G_VARIANT_CLASS_INT16:
+- return g_variant_new_int16 (g_variant_get_int16 (value));
++ if (byteswap)
++ return g_variant_new_int16 (GUINT16_SWAP_LE_BE (g_variant_get_int16 (value)));
++ else
++ return g_variant_new_int16 (g_variant_get_int16 (value));
+
+ case G_VARIANT_CLASS_UINT16:
+- return g_variant_new_uint16 (g_variant_get_uint16 (value));
++ if (byteswap)
++ return g_variant_new_uint16 (GUINT16_SWAP_LE_BE (g_variant_get_uint16 (value)));
++ else
++ return g_variant_new_uint16 (g_variant_get_uint16 (value));
+
+ case G_VARIANT_CLASS_INT32:
+- return g_variant_new_int32 (g_variant_get_int32 (value));
++ if (byteswap)
++ return g_variant_new_int32 (GUINT32_SWAP_LE_BE (g_variant_get_int32 (value)));
++ else
++ return g_variant_new_int32 (g_variant_get_int32 (value));
+
+ case G_VARIANT_CLASS_UINT32:
+- return g_variant_new_uint32 (g_variant_get_uint32 (value));
++ if (byteswap)
++ return g_variant_new_uint32 (GUINT32_SWAP_LE_BE (g_variant_get_uint32 (value)));
++ else
++ return g_variant_new_uint32 (g_variant_get_uint32 (value));
+
+ case G_VARIANT_CLASS_INT64:
+- return g_variant_new_int64 (g_variant_get_int64 (value));
++ if (byteswap)
++ return g_variant_new_int64 (GUINT64_SWAP_LE_BE (g_variant_get_int64 (value)));
++ else
++ return g_variant_new_int64 (g_variant_get_int64 (value));
+
+ case G_VARIANT_CLASS_UINT64:
+- return g_variant_new_uint64 (g_variant_get_uint64 (value));
++ if (byteswap)
++ return g_variant_new_uint64 (GUINT64_SWAP_LE_BE (g_variant_get_uint64 (value)));
++ else
++ return g_variant_new_uint64 (g_variant_get_uint64 (value));
+
+ case G_VARIANT_CLASS_HANDLE:
+- return g_variant_new_handle (g_variant_get_handle (value));
++ if (byteswap)
++ return g_variant_new_handle (GUINT32_SWAP_LE_BE (g_variant_get_handle (value)));
++ else
++ return g_variant_new_handle (g_variant_get_handle (value));
+
+ case G_VARIANT_CLASS_DOUBLE:
+- return g_variant_new_double (g_variant_get_double (value));
++ if (byteswap)
++ {
++ /* We have to convert the double to a uint64 here using a union,
++ * because a cast will round it numerically. */
++ union
++ {
++ guint64 u64;
++ gdouble dbl;
++ } u1, u2;
++ u1.dbl = g_variant_get_double (value);
++ u2.u64 = GUINT64_SWAP_LE_BE (u1.u64);
++ return g_variant_new_double (u2.dbl);
++ }
++ else
++ return g_variant_new_double (g_variant_get_double (value));
+
+ case G_VARIANT_CLASS_STRING:
+ return g_variant_new_string (g_variant_get_string (value, NULL));
+@@ -6026,7 +6062,7 @@ g_variant_get_normal_form (GVariant *value)
+ if (g_variant_is_normal_form (value))
+ return g_variant_ref (value);
+
+- trusted = g_variant_deep_copy (value);
++ trusted = g_variant_deep_copy (value, FALSE);
+ g_assert (g_variant_is_trusted (trusted));
+
+ return g_variant_ref_sink (trusted);
+@@ -6046,6 +6082,11 @@ g_variant_get_normal_form (GVariant *value)
+ * contain multi-byte numeric data. That include strings, booleans,
+ * bytes and containers containing only these things (recursively).
+ *
++ * While this function can safely handle untrusted, non-normal data, it is
++ * recommended to check whether the input is in normal form beforehand, using
++ * g_variant_is_normal_form(), and to reject non-normal inputs if your
++ * application can be strict about what inputs it rejects.
++ *
+ * The returned value is always in normal form and is marked as trusted.
+ *
+ * Returns: (transfer full): the byteswapped form of @value
+@@ -6063,22 +6104,21 @@ g_variant_byteswap (GVariant *value)
+
+ g_variant_type_info_query (type_info, &alignment, NULL);
+
+- if (alignment)
+- /* (potentially) contains multi-byte numeric data */
++ if (alignment && g_variant_is_normal_form (value))
+ {
++ /* (potentially) contains multi-byte numeric data, but is also already in
++ * normal form so we can use a faster byteswapping codepath on the
++ * serialised data */
+ GVariantSerialised serialised = { 0, };
+- GVariant *trusted;
+ GBytes *bytes;
+
+- trusted = g_variant_get_normal_form (value);
+- serialised.type_info = g_variant_get_type_info (trusted);
+- serialised.size = g_variant_get_size (trusted);
++ serialised.type_info = g_variant_get_type_info (value);
++ serialised.size = g_variant_get_size (value);
+ serialised.data = g_malloc (serialised.size);
+- serialised.depth = g_variant_get_depth (trusted);
++ serialised.depth = g_variant_get_depth (value);
+ serialised.ordered_offsets_up_to = G_MAXSIZE; /* operating on the normal form */
+ serialised.checked_offsets_up_to = G_MAXSIZE;
+- g_variant_store (trusted, serialised.data);
+- g_variant_unref (trusted);
++ g_variant_store (value, serialised.data);
+
+ g_variant_serialised_byteswap (serialised);
+
+@@ -6086,6 +6126,9 @@ g_variant_byteswap (GVariant *value)
+ new = g_variant_ref_sink (g_variant_new_from_bytes (g_variant_get_type (value), bytes, TRUE));
+ g_bytes_unref (bytes);
+ }
++ else if (alignment)
++ /* (potentially) contains multi-byte numeric data */
++ new = g_variant_ref_sink (g_variant_deep_copy (value, TRUE));
+ else
+ /* contains no multi-byte data */
+ new = g_variant_get_normal_form (value);
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index bea16b5e58..ba837af48d 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -2288,24 +2288,67 @@ serialise_tree (TreeInstance *tree,
+ static void
+ test_byteswap (void)
+ {
+- GVariantSerialised one = { 0, }, two = { 0, };
++ GVariantSerialised one = { 0, }, two = { 0, }, three = { 0, };
+ TreeInstance *tree;
+-
++ GVariant *one_variant = NULL;
++ GVariant *two_variant = NULL;
++ GVariant *two_byteswapped = NULL;
++ GVariant *three_variant = NULL;
++ GVariant *three_byteswapped = NULL;
++ guint8 *three_data_copy = NULL;
++ gsize three_size_copy = 0;
++
++ /* Write a tree out twice, once normally and once byteswapped. */
+ tree = tree_instance_new (NULL, 3);
+ serialise_tree (tree, &one);
+
++ one_variant = g_variant_new_from_data (G_VARIANT_TYPE (g_variant_type_info_get_type_string (one.type_info)),
++ one.data, one.size, FALSE, NULL, NULL);
++
+ i_am_writing_byteswapped = TRUE;
+ serialise_tree (tree, &two);
++ serialise_tree (tree, &three);
+ i_am_writing_byteswapped = FALSE;
+
+- g_variant_serialised_byteswap (two);
+-
+- g_assert_cmpmem (one.data, one.size, two.data, two.size);
+- g_assert_cmpuint (one.depth, ==, two.depth);
+-
++ /* Swap the first byteswapped one back using the function we want to test. */
++ two_variant = g_variant_new_from_data (G_VARIANT_TYPE (g_variant_type_info_get_type_string (two.type_info)),
++ two.data, two.size, FALSE, NULL, NULL);
++ two_byteswapped = g_variant_byteswap (two_variant);
++
++ /* Make the second byteswapped one non-normal (hopefully), and then byteswap
++ * it back using the function we want to test in its non-normal mode.
++ * This might not work because it’s not necessarily possible to make an
++ * arbitrary random variant non-normal. Adding a single zero byte to the end
++ * often makes something non-normal but still readable. */
++ three_size_copy = three.size + 1;
++ three_data_copy = g_malloc (three_size_copy);
++ memcpy (three_data_copy, three.data, three.size);
++ three_data_copy[three.size] = '\0';
++
++ three_variant = g_variant_new_from_data (G_VARIANT_TYPE (g_variant_type_info_get_type_string (three.type_info)),
++ three_data_copy, three_size_copy, FALSE, NULL, NULL);
++ three_byteswapped = g_variant_byteswap (three_variant);
++
++ /* Check they’re the same. We can always compare @one_variant and
++ * @two_byteswapped. We can only compare @two_byteswapped and
++ * @three_byteswapped if @two_variant and @three_variant are equal: in that
++ * case, the corruption to @three_variant was enough to make it non-normal but
++ * not enough to change its value. */
++ g_assert_cmpvariant (one_variant, two_byteswapped);
++
++ if (g_variant_equal (two_variant, three_variant))
++ g_assert_cmpvariant (two_byteswapped, three_byteswapped);
++
++ g_variant_unref (three_byteswapped);
++ g_variant_unref (three_variant);
++ g_variant_unref (two_byteswapped);
++ g_variant_unref (two_variant);
++ g_variant_unref (one_variant);
+ tree_instance_free (tree);
+ g_free (one.data);
+ g_free (two.data);
++ g_free (three.data);
++ g_free (three_data_copy);
+ }
+
+ static void
+--
+GitLab
+
+
diff --git a/dev-libs/glib/glib-2.68.3-r2.ebuild b/dev-libs/glib/glib-2.68.3-r2.ebuild
index 03f963d..958d58b 100644
--- a/dev-libs/glib/glib-2.68.3-r2.ebuild
+++ b/dev-libs/glib/glib-2.68.3-r2.ebuild
@@ -73,6 +73,8 @@
"${FILESDIR}"/${PN}-2.64.1-mark-gdbus-server-auth-test-flaky.patch
"${FILESDIR}/glib-2.66.7-CHROMIUM-gdbus-system-bus-address.patch"
"${FILESDIR}"/${PN}-2.68.3-glibc-2.34-close_range.patch
+ "${FILESDIR}"/glib-2.74.1-normal-form-handling-gvariant.patch
+ "${FILESDIR}"/glib-2.74.1-normal-form-handling-gvariant-patch-fixes.patch
)
pkg_setup() {
diff --git a/dev-util/glib-utils/files/glib-2.74.1-normal-form-handling-gvariant-patch-fixes.patch b/dev-util/glib-utils/files/glib-2.74.1-normal-form-handling-gvariant-patch-fixes.patch
new file mode 100644
index 0000000..612d2ab
--- /dev/null
+++ b/dev-util/glib-utils/files/glib-2.74.1-normal-form-handling-gvariant-patch-fixes.patch
@@ -0,0 +1,200 @@
+From bf008669f26f70426ca30ee8cdcbd48e41a74718 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Thu, 15 Dec 2022 13:00:39 +0000
+Subject: [PATCH 1/2] =?UTF-8?q?gvariant:=20Check=20offset=20table=20doesn?=
+ =?UTF-8?q?=E2=80=99t=20fall=20outside=20variant=20bounds?=
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+When dereferencing the first entry in the offset table for a tuple,
+check that it doesn’t fall outside the bounds of the variant first.
+
+This prevents an out-of-bounds read from some non-normal tuples.
+
+This bug was introduced in commit 73d0aa81c2575a5c9ae77d.
+
+Includes a unit test, although the test will likely only catch the
+original bug if run with asan enabled.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Fixes: #2840
+oss-fuzz#54302
+---
+ glib/gvariant-serialiser.c | 12 ++++++--
+ glib/tests/gvariant.c | 63 ++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 72 insertions(+), 3 deletions(-)
+
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index e9b0eab2b4..af863cc526 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -984,7 +984,8 @@ gvs_tuple_get_member_bounds (GVariantSerialised value,
+
+ member_info = g_variant_type_info_member_info (value.type_info, index_);
+
+- if (member_info->i + 1)
++ if (member_info->i + 1 &&
++ offset_size * (member_info->i + 1) <= value.size)
+ member_start = gvs_read_unaligned_le (value.data + value.size -
+ offset_size * (member_info->i + 1),
+ offset_size);
+@@ -995,7 +996,8 @@ gvs_tuple_get_member_bounds (GVariantSerialised value,
+ member_start &= member_info->b;
+ member_start |= member_info->c;
+
+- if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_LAST)
++ if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_LAST &&
++ offset_size * (member_info->i + 1) <= value.size)
+ member_end = value.size - offset_size * (member_info->i + 1);
+
+ else if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_FIXED)
+@@ -1006,11 +1008,15 @@ gvs_tuple_get_member_bounds (GVariantSerialised value,
+ member_end = member_start + fixed_size;
+ }
+
+- else /* G_VARIANT_MEMBER_ENDING_OFFSET */
++ else if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_OFFSET &&
++ offset_size * (member_info->i + 2) <= value.size)
+ member_end = gvs_read_unaligned_le (value.data + value.size -
+ offset_size * (member_info->i + 2),
+ offset_size);
+
++ else /* invalid */
++ member_end = G_MAXSIZE;
++
+ if (out_member_start != NULL)
+ *out_member_start = member_start;
+ if (out_member_end != NULL)
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index ba837af48d..e4a2518aa6 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -5562,6 +5562,67 @@ test_normal_checking_tuple_offsets4 (void)
+ g_variant_unref (variant);
+ }
+
++/* This is a regression test that dereferencing the first element in the offset
++ * table doesn’t dereference memory before the start of the GVariant. The first
++ * element in the offset table gives the offset of the final member in the
++ * tuple (the offset table is stored in reverse), and the position of this final
++ * member is needed to check that none of the tuple members overlap with the
++ * offset table
++ *
++ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2840 */
++static void
++test_normal_checking_tuple_offsets5 (void)
++{
++ /* A tuple of type (sss) in normal form would have an offset table with two
++ * entries:
++ * - The first entry (lowest index in the table) gives the offset of the
++ * third `s` in the tuple, as the offset table is reversed compared to the
++ * tuple members.
++ * - The second entry (highest index in the table) gives the offset of the
++ * second `s` in the tuple.
++ * - The offset of the first `s` in the tuple is always 0.
++ *
++ * See §2.5.4 (Structures) of the GVariant specification for details, noting
++ * that the table is only layed out this way because all three members of the
++ * tuple have non-fixed sizes.
++ *
++ * It’s not clear whether the 0xaa data of this variant is part of the strings
++ * in the tuple, or part of the offset table. It doesn’t really matter. This
++ * is a regression test to check that the code to validate the offset table
++ * doesn’t unconditionally try to access the first entry in the offset table
++ * by subtracting the table size from the end of the GVariant data.
++ *
++ * In this non-normal case, that would result in an address off the start of
++ * the GVariant data, and an out-of-bounds read, because the GVariant is one
++ * byte long, but the offset table is calculated as two bytes long (with 1B
++ * sized entries) from the tuple’s type.
++ */
++ const GVariantType *data_type = G_VARIANT_TYPE ("(sss)");
++ const guint8 data[] = { 0xaa };
++ gsize size = sizeof (data);
++ GVariant *variant = NULL;
++ GVariant *normal_variant = NULL;
++ GVariant *expected = NULL;
++
++ g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2840");
++
++ variant = g_variant_new_from_data (data_type, data, size, FALSE, NULL, NULL);
++ g_assert_nonnull (variant);
++
++ g_assert_false (g_variant_is_normal_form (variant));
++
++ normal_variant = g_variant_get_normal_form (variant);
++ g_assert_nonnull (normal_variant);
++
++ expected = g_variant_new_parsed ("('', '', '')");
++ g_assert_cmpvariant (expected, variant);
++ g_assert_cmpvariant (expected, normal_variant);
++
++ g_variant_unref (expected);
++ g_variant_unref (normal_variant);
++ g_variant_unref (variant);
++}
++
+ /* Test that an otherwise-valid serialised GVariant is considered non-normal if
+ * its offset table entries are too wide.
+ *
+@@ -5813,6 +5874,8 @@ main (int argc, char **argv)
+ test_normal_checking_tuple_offsets3);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets4",
+ test_normal_checking_tuple_offsets4);
++ g_test_add_func ("/gvariant/normal-checking/tuple-offsets5",
++ test_normal_checking_tuple_offsets5);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets/minimal-sized",
+ test_normal_checking_tuple_offsets_minimal_sized);
+ g_test_add_func ("/gvariant/normal-checking/empty-object-path",
+--
+GitLab
+
+
+From 4d0bed8c4690f7a2692474ef6a570bd99ef45ef1 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Thu, 15 Dec 2022 16:49:28 +0000
+Subject: [PATCH 2/2] gvariant: Propagate trust when getting a child of a
+ serialised variant
+
+If a variant is trusted, that means all its children are trusted, so
+ensure that their checked offsets are set as such.
+
+This allows a lot of the offset table checks to be avoided when getting
+children from trusted serialised tuples, which speeds things up.
+
+No unit test is included because this is just a performance fix. If
+there are other slownesses, or regressions, in serialised `GVariant`
+performance, the fuzzing setup will catch them like it did this one.
+
+This change does reduce the time to run the oss-fuzz reproducer from 80s
+to about 0.7s on my machine.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Fixes: #2841
+oss-fuzz#54314
+---
+ glib/gvariant-core.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index 75d23f39af..57bbf88cc9 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -1198,8 +1198,8 @@ g_variant_get_child_value (GVariant *value,
+ child->contents.serialised.bytes =
+ g_bytes_ref (value->contents.serialised.bytes);
+ child->contents.serialised.data = s_child.data;
+- child->contents.serialised.ordered_offsets_up_to = s_child.ordered_offsets_up_to;
+- child->contents.serialised.checked_offsets_up_to = s_child.checked_offsets_up_to;
++ child->contents.serialised.ordered_offsets_up_to = (value->state & STATE_TRUSTED) ? G_MAXSIZE : s_child.ordered_offsets_up_to;
++ child->contents.serialised.checked_offsets_up_to = (value->state & STATE_TRUSTED) ? G_MAXSIZE : s_child.checked_offsets_up_to;
+
+ return child;
+ }
+--
+GitLab
+
+
diff --git a/dev-util/glib-utils/files/glib-2.74.1-normal-form-handling-gvariant.patch b/dev-util/glib-utils/files/glib-2.74.1-normal-form-handling-gvariant.patch
new file mode 100644
index 0000000..1d75331
--- /dev/null
+++ b/dev-util/glib-utils/files/glib-2.74.1-normal-form-handling-gvariant.patch
@@ -0,0 +1,2975 @@
+From 590f7a6b76b5a3695aa562c9c3ab244433b6624b Mon Sep 17 00:00:00 2001
+From: William Manley <will@stb-tester.com>
+Date: Tue, 23 Jun 2020 22:59:58 +0100
+Subject: [PATCH 01/18] gvariant-core: Consolidate construction of
+ `GVariantSerialised`
+
+So I only need to change it in one place.
+
+This introduces no functional changes.
+
+Helps: #2121
+---
+ glib/gvariant-core.c | 49 ++++++++++++++++++++++----------------------
+ 1 file changed, 25 insertions(+), 24 deletions(-)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index ad0bab5b62..70c712ddc0 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -351,6 +351,27 @@ g_variant_ensure_size (GVariant *value)
+ }
+ }
+
++/* < private >
++ * g_variant_to_serialised:
++ * @value: a #GVariant
++ *
++ * Gets a GVariantSerialised for a GVariant in state STATE_SERIALISED.
++ */
++inline static GVariantSerialised
++g_variant_to_serialised (GVariant *value)
++{
++ g_assert (value->state & STATE_SERIALISED);
++ {
++ GVariantSerialised serialised = {
++ value->type_info,
++ (gpointer) value->contents.serialised.data,
++ value->size,
++ value->depth,
++ };
++ return serialised;
++ }
++}
++
+ /* < private >
+ * g_variant_serialise:
+ * @value: a #GVariant
+@@ -1009,16 +1030,8 @@ g_variant_n_children (GVariant *value)
+ g_variant_lock (value);
+
+ if (value->state & STATE_SERIALISED)
+- {
+- GVariantSerialised serialised = {
+- value->type_info,
+- (gpointer) value->contents.serialised.data,
+- value->size,
+- value->depth,
+- };
+-
+- n_children = g_variant_serialised_n_children (serialised);
+- }
++ n_children = g_variant_serialised_n_children (
++ g_variant_to_serialised (value));
+ else
+ n_children = value->contents.tree.n_children;
+
+@@ -1085,12 +1098,7 @@ g_variant_get_child_value (GVariant *value,
+ }
+
+ {
+- GVariantSerialised serialised = {
+- value->type_info,
+- (gpointer) value->contents.serialised.data,
+- value->size,
+- value->depth,
+- };
++ GVariantSerialised serialised = g_variant_to_serialised (value);
+ GVariantSerialised s_child;
+ GVariant *child;
+
+@@ -1203,14 +1211,7 @@ g_variant_is_normal_form (GVariant *value)
+
+ if (value->state & STATE_SERIALISED)
+ {
+- GVariantSerialised serialised = {
+- value->type_info,
+- (gpointer) value->contents.serialised.data,
+- value->size,
+- value->depth
+- };
+-
+- if (g_variant_serialised_is_normal (serialised))
++ if (g_variant_serialised_is_normal (g_variant_to_serialised (value)))
+ value->state |= STATE_TRUSTED;
+ }
+ else
+--
+GitLab
+
+
+From f8f5d8eefa06008aa8fe684069193dc3b1ae1b58 Mon Sep 17 00:00:00 2001
+From: William Manley <will@stb-tester.com>
+Date: Thu, 25 Jun 2020 17:08:21 +0100
+Subject: [PATCH 02/18] gvariant-serialiser: Factor out functions for dealing
+ with framing offsets
+
+This introduces no functional changes.
+
+Helps: #2121
+---
+ glib/gvariant-serialiser.c | 108 +++++++++++++++++++------------------
+ 1 file changed, 57 insertions(+), 51 deletions(-)
+
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index 3af68b79b2..09492e4526 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -635,30 +635,62 @@ gvs_calculate_total_size (gsize body_size,
+ return body_size + 8 * offsets;
+ }
+
++struct Offsets
++{
++ gsize data_size;
++
++ guchar *array;
++ gsize length;
++ guint offset_size;
++
++ gboolean is_normal;
++};
++
+ static gsize
+-gvs_variable_sized_array_n_children (GVariantSerialised value)
++gvs_offsets_get_offset_n (struct Offsets *offsets,
++ gsize n)
++{
++ return gvs_read_unaligned_le (
++ offsets->array + (offsets->offset_size * n), offsets->offset_size);
++}
++
++static struct Offsets
++gvs_variable_sized_array_get_frame_offsets (GVariantSerialised value)
+ {
++ struct Offsets out = { 0, };
+ gsize offsets_array_size;
+- gsize offset_size;
+ gsize last_end;
+
+ if (value.size == 0)
+- return 0;
+-
+- offset_size = gvs_get_offset_size (value.size);
++ {
++ out.is_normal = TRUE;
++ return out;
++ }
+
+- last_end = gvs_read_unaligned_le (value.data + value.size -
+- offset_size, offset_size);
++ out.offset_size = gvs_get_offset_size (value.size);
++ last_end = gvs_read_unaligned_le (value.data + value.size - out.offset_size,
++ out.offset_size);
+
+ if (last_end > value.size)
+- return 0;
++ return out; /* offsets not normal */
+
+ offsets_array_size = value.size - last_end;
+
+- if (offsets_array_size % offset_size)
+- return 0;
++ if (offsets_array_size % out.offset_size)
++ return out; /* offsets not normal */
++
++ out.data_size = last_end;
++ out.array = value.data + last_end;
++ out.length = offsets_array_size / out.offset_size;
++ out.is_normal = TRUE;
+
+- return offsets_array_size / offset_size;
++ return out;
++}
++
++static gsize
++gvs_variable_sized_array_n_children (GVariantSerialised value)
++{
++ return gvs_variable_sized_array_get_frame_offsets (value).length;
+ }
+
+ static GVariantSerialised
+@@ -666,8 +698,9 @@ gvs_variable_sized_array_get_child (GVariantSerialised value,
+ gsize index_)
+ {
+ GVariantSerialised child = { 0, };
+- gsize offset_size;
+- gsize last_end;
++
++ struct Offsets offsets = gvs_variable_sized_array_get_frame_offsets (value);
++
+ gsize start;
+ gsize end;
+
+@@ -675,18 +708,11 @@ gvs_variable_sized_array_get_child (GVariantSerialised value,
+ g_variant_type_info_ref (child.type_info);
+ child.depth = value.depth + 1;
+
+- offset_size = gvs_get_offset_size (value.size);
+-
+- last_end = gvs_read_unaligned_le (value.data + value.size -
+- offset_size, offset_size);
+-
+ if (index_ > 0)
+ {
+ guint alignment;
+
+- start = gvs_read_unaligned_le (value.data + last_end +
+- (offset_size * (index_ - 1)),
+- offset_size);
++ start = gvs_offsets_get_offset_n (&offsets, index_ - 1);
+
+ g_variant_type_info_query (child.type_info, &alignment, NULL);
+ start += (-start) & alignment;
+@@ -694,11 +720,9 @@ gvs_variable_sized_array_get_child (GVariantSerialised value,
+ else
+ start = 0;
+
+- end = gvs_read_unaligned_le (value.data + last_end +
+- (offset_size * index_),
+- offset_size);
++ end = gvs_offsets_get_offset_n (&offsets, index_);
+
+- if (start < end && end <= value.size && end <= last_end)
++ if (start < end && end <= value.size && end <= offsets.data_size)
+ {
+ child.data = value.data + start;
+ child.size = end - start;
+@@ -770,34 +794,16 @@ static gboolean
+ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+ {
+ GVariantSerialised child = { 0, };
+- gsize offsets_array_size;
+- guchar *offsets_array;
+- guint offset_size;
+ guint alignment;
+- gsize last_end;
+- gsize length;
+ gsize offset;
+ gsize i;
+
+- if (value.size == 0)
+- return TRUE;
+-
+- offset_size = gvs_get_offset_size (value.size);
+- last_end = gvs_read_unaligned_le (value.data + value.size -
+- offset_size, offset_size);
++ struct Offsets offsets = gvs_variable_sized_array_get_frame_offsets (value);
+
+- if (last_end > value.size)
++ if (!offsets.is_normal)
+ return FALSE;
+
+- offsets_array_size = value.size - last_end;
+-
+- if (offsets_array_size % offset_size)
+- return FALSE;
+-
+- offsets_array = value.data + value.size - offsets_array_size;
+- length = offsets_array_size / offset_size;
+-
+- if (length == 0)
++ if (value.size != 0 && offsets.length == 0)
+ return FALSE;
+
+ child.type_info = g_variant_type_info_element (value.type_info);
+@@ -805,14 +811,14 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+ child.depth = value.depth + 1;
+ offset = 0;
+
+- for (i = 0; i < length; i++)
++ for (i = 0; i < offsets.length; i++)
+ {
+ gsize this_end;
+
+- this_end = gvs_read_unaligned_le (offsets_array + offset_size * i,
+- offset_size);
++ this_end = gvs_read_unaligned_le (offsets.array + offsets.offset_size * i,
++ offsets.offset_size);
+
+- if (this_end < offset || this_end > last_end)
++ if (this_end < offset || this_end > offsets.data_size)
+ return FALSE;
+
+ while (offset & alignment)
+@@ -834,7 +840,7 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+ offset = this_end;
+ }
+
+- g_assert (offset == last_end);
++ g_assert (offset == offsets.data_size);
+
+ return TRUE;
+ }
+--
+GitLab
+
+
+From 5c27f22aff636766fbc5e49927fd28213629e840 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Tue, 25 Oct 2022 18:05:52 +0100
+Subject: [PATCH 03/18] gvariant: Zero-initialise various GVariantSerialised
+ objects
+
+The following few commits will add a couple of new fields to
+`GVariantSerialised`, and they should be zero-filled by default.
+
+Try and pre-empt that a bit by zero-filling `GVariantSerialised` by
+default in a few places.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2121
+---
+ glib/gvariant.c | 2 +-
+ glib/tests/gvariant.c | 12 ++++++------
+ 2 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index 93eb6cb6bc..6912eebe05 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -6001,7 +6001,7 @@ g_variant_byteswap (GVariant *value)
+ if (alignment)
+ /* (potentially) contains multi-byte numeric data */
+ {
+- GVariantSerialised serialised;
++ GVariantSerialised serialised = { 0, };
+ GVariant *trusted;
+ GBytes *bytes;
+
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 29110e4e24..1a57dc6ef7 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -1440,7 +1440,7 @@ test_maybe (void)
+
+ for (flavour = 0; flavour < 8; flavour += alignment)
+ {
+- GVariantSerialised serialised;
++ GVariantSerialised serialised = { 0, };
+ GVariantSerialised child;
+
+ serialised.type_info = type_info;
+@@ -1564,7 +1564,7 @@ test_array (void)
+
+ for (flavour = 0; flavour < 8; flavour += alignment)
+ {
+- GVariantSerialised serialised;
++ GVariantSerialised serialised = { 0, };
+
+ serialised.type_info = array_info;
+ serialised.data = flavoured_malloc (needed_size, flavour);
+@@ -1728,7 +1728,7 @@ test_tuple (void)
+
+ for (flavour = 0; flavour < 8; flavour += alignment)
+ {
+- GVariantSerialised serialised;
++ GVariantSerialised serialised = { 0, };
+
+ serialised.type_info = type_info;
+ serialised.data = flavoured_malloc (needed_size, flavour);
+@@ -1823,7 +1823,7 @@ test_variant (void)
+
+ for (flavour = 0; flavour < 8; flavour += alignment)
+ {
+- GVariantSerialised serialised;
++ GVariantSerialised serialised = { 0, };
+ GVariantSerialised child;
+
+ serialised.type_info = type_info;
+@@ -2270,7 +2270,7 @@ serialise_tree (TreeInstance *tree,
+ static void
+ test_byteswap (void)
+ {
+- GVariantSerialised one, two;
++ GVariantSerialised one = { 0, }, two = { 0, };
+ TreeInstance *tree;
+
+ tree = tree_instance_new (NULL, 3);
+@@ -2344,7 +2344,7 @@ test_serialiser_children (void)
+ static void
+ test_fuzz (gdouble *fuzziness)
+ {
+- GVariantSerialised serialised;
++ GVariantSerialised serialised = { 0, };
+ TreeInstance *tree;
+
+ /* make an instance */
+--
+GitLab
+
+
+From c8067857f7c8fb369ecb30bb534b018b2e2b8f87 Mon Sep 17 00:00:00 2001
+From: William Manley <will@stb-tester.com>
+Date: Mon, 29 Jun 2020 16:59:44 +0100
+Subject: [PATCH 04/18] =?UTF-8?q?gvariant:=20Don=E2=80=99t=20allow=20child?=
+ =?UTF-8?q?=20elements=20to=20overlap=20with=20each=20other?=
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+If different elements of a variable sized array can overlap with each
+other then we can cause a `GVariant` to normalise to a much larger type.
+
+This commit changes the behaviour of `GVariant` with non-normal form data. If
+an invalid frame offset is found all subsequent elements are given their
+default value.
+
+When retrieving an element at index `n` we scan the frame offsets up to index
+`n` and if they are not in order we return an element with the default value
+for that type. This guarantees that elements don't overlap with each
+other. We remember the offset we've scanned up to so we don't need to
+repeat this work on subsequent accesses. We skip these checks for trusted
+data.
+
+Unfortunately this makes random access of untrusted data O(n) — at least
+on first access. It doesn't affect the algorithmic complexity of accessing
+elements in order, such as when using the `GVariantIter` interface. Also:
+the cost of validation will be amortised as the `GVariant` instance is
+continued to be used.
+
+I've implemented this with 4 different functions, 1 for each element size,
+rather than looping calling `gvs_read_unaligned_le` in the hope that the
+compiler will find it easy to optimise and should produce fairly tight
+code.
+
+Fixes: #2121
+---
+ glib/gvariant-core.c | 35 ++++++++++++++++
+ glib/gvariant-serialiser.c | 86 ++++++++++++++++++++++++++++++++++++--
+ glib/gvariant-serialiser.h | 9 ++++
+ glib/tests/gvariant.c | 45 ++++++++++++++++++++
+ 4 files changed, 172 insertions(+), 3 deletions(-)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index 70c712ddc0..a75e7db9ea 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -67,6 +67,7 @@ struct _GVariant
+ {
+ GBytes *bytes;
+ gconstpointer data;
++ gsize ordered_offsets_up_to;
+ } serialised;
+
+ struct
+@@ -164,6 +165,24 @@ struct _GVariant
+ * if .data pointed to the appropriate number of nul
+ * bytes.
+ *
++ * .ordered_offsets_up_to: If ordered_offsets_up_to == n this means that all
++ * the frame offsets up to and including the frame
++ * offset determining the end of element n are in
++ * order. This guarantees that the bytes of element
++ * n don't overlap with any previous element.
++ *
++ * For trusted data this is set to G_MAXSIZE and we
++ * don't check that the frame offsets are in order.
++ *
++ * Note: This doesn't imply the offsets are good in
++ * any way apart from their ordering. In particular
++ * offsets may be out of bounds for this value or
++ * may imply that the data overlaps the frame
++ * offsets themselves.
++ *
++ * This field is only relevant for arrays of non
++ * fixed width types.
++ *
+ * .tree: Only valid when the instance is in tree form.
+ *
+ * Note that accesses from other threads could result in
+@@ -367,6 +386,7 @@ g_variant_to_serialised (GVariant *value)
+ (gpointer) value->contents.serialised.data,
+ value->size,
+ value->depth,
++ value->contents.serialised.ordered_offsets_up_to,
+ };
+ return serialised;
+ }
+@@ -398,6 +418,7 @@ g_variant_serialise (GVariant *value,
+ serialised.size = value->size;
+ serialised.data = data;
+ serialised.depth = value->depth;
++ serialised.ordered_offsets_up_to = 0;
+
+ children = (gpointer *) value->contents.tree.children;
+ n_children = value->contents.tree.n_children;
+@@ -441,6 +462,15 @@ g_variant_fill_gvs (GVariantSerialised *serialised,
+ g_assert (serialised->size == value->size);
+ serialised->depth = value->depth;
+
++ if (value->state & STATE_SERIALISED)
++ {
++ serialised->ordered_offsets_up_to = value->contents.serialised.ordered_offsets_up_to;
++ }
++ else
++ {
++ serialised->ordered_offsets_up_to = 0;
++ }
++
+ if (serialised->data)
+ /* g_variant_store() is a public API, so it
+ * it will reacquire the lock if it needs to.
+@@ -483,6 +513,7 @@ g_variant_ensure_serialised (GVariant *value)
+ bytes = g_bytes_new_take (data, value->size);
+ value->contents.serialised.data = g_bytes_get_data (bytes, NULL);
+ value->contents.serialised.bytes = bytes;
++ value->contents.serialised.ordered_offsets_up_to = G_MAXSIZE;
+ value->state |= STATE_SERIALISED;
+ }
+ }
+@@ -563,6 +594,7 @@ g_variant_new_from_bytes (const GVariantType *type,
+ serialised.type_info = value->type_info;
+ serialised.data = (guchar *) g_bytes_get_data (bytes, &serialised.size);
+ serialised.depth = 0;
++ serialised.ordered_offsets_up_to = trusted ? G_MAXSIZE : 0;
+
+ if (!g_variant_serialised_check (serialised))
+ {
+@@ -613,6 +645,8 @@ g_variant_new_from_bytes (const GVariantType *type,
+ value->contents.serialised.data = g_bytes_get_data (bytes, &value->size);
+ }
+
++ value->contents.serialised.ordered_offsets_up_to = trusted ? G_MAXSIZE : 0;
++
+ g_clear_pointer (&owned_bytes, g_bytes_unref);
+
+ return value;
+@@ -1132,6 +1166,7 @@ g_variant_get_child_value (GVariant *value,
+ child->contents.serialised.bytes =
+ g_bytes_ref (value->contents.serialised.bytes);
+ child->contents.serialised.data = s_child.data;
++ child->contents.serialised.ordered_offsets_up_to = s_child.ordered_offsets_up_to;
+
+ return child;
+ }
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index 09492e4526..1aa2b8fa9b 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -1,6 +1,7 @@
+ /*
+ * Copyright © 2007, 2008 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
++ * Copyright © 2020 William Manley
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+@@ -266,6 +267,7 @@ gvs_fixed_sized_maybe_get_child (GVariantSerialised value,
+ value.type_info = g_variant_type_info_element (value.type_info);
+ g_variant_type_info_ref (value.type_info);
+ value.depth++;
++ value.ordered_offsets_up_to = 0;
+
+ return value;
+ }
+@@ -297,7 +299,7 @@ gvs_fixed_sized_maybe_serialise (GVariantSerialised value,
+ {
+ if (n_children)
+ {
+- GVariantSerialised child = { NULL, value.data, value.size, value.depth + 1 };
++ GVariantSerialised child = { NULL, value.data, value.size, value.depth + 1, 0 };
+
+ gvs_filler (&child, children[0]);
+ }
+@@ -319,6 +321,7 @@ gvs_fixed_sized_maybe_is_normal (GVariantSerialised value)
+ /* proper element size: "Just". recurse to the child. */
+ value.type_info = g_variant_type_info_element (value.type_info);
+ value.depth++;
++ value.ordered_offsets_up_to = 0;
+
+ return g_variant_serialised_is_normal (value);
+ }
+@@ -360,6 +363,7 @@ gvs_variable_sized_maybe_get_child (GVariantSerialised value,
+ value.data = NULL;
+
+ value.depth++;
++ value.ordered_offsets_up_to = 0;
+
+ return value;
+ }
+@@ -390,7 +394,7 @@ gvs_variable_sized_maybe_serialise (GVariantSerialised value,
+ {
+ if (n_children)
+ {
+- GVariantSerialised child = { NULL, value.data, value.size - 1, value.depth + 1 };
++ GVariantSerialised child = { NULL, value.data, value.size - 1, value.depth + 1, 0 };
+
+ /* write the data for the child. */
+ gvs_filler (&child, children[0]);
+@@ -410,6 +414,7 @@ gvs_variable_sized_maybe_is_normal (GVariantSerialised value)
+ value.type_info = g_variant_type_info_element (value.type_info);
+ value.size--;
+ value.depth++;
++ value.ordered_offsets_up_to = 0;
+
+ return g_variant_serialised_is_normal (value);
+ }
+@@ -693,6 +698,32 @@ gvs_variable_sized_array_n_children (GVariantSerialised value)
+ return gvs_variable_sized_array_get_frame_offsets (value).length;
+ }
+
++/* Find the index of the first out-of-order element in @data, assuming that
++ * @data is an array of elements of given @type, starting at index @start and
++ * containing a further @len-@start elements. */
++#define DEFINE_FIND_UNORDERED(type) \
++ static gsize \
++ find_unordered_##type (const guint8 *data, gsize start, gsize len) \
++ { \
++ gsize off; \
++ type current, previous; \
++ \
++ memcpy (&previous, data + start * sizeof (current), sizeof (current)); \
++ for (off = (start + 1) * sizeof (current); off < len * sizeof (current); off += sizeof (current)) \
++ { \
++ memcpy (¤t, data + off, sizeof (current)); \
++ if (current < previous) \
++ break; \
++ previous = current; \
++ } \
++ return off / sizeof (current) - 1; \
++ }
++
++DEFINE_FIND_UNORDERED (guint8);
++DEFINE_FIND_UNORDERED (guint16);
++DEFINE_FIND_UNORDERED (guint32);
++DEFINE_FIND_UNORDERED (guint64);
++
+ static GVariantSerialised
+ gvs_variable_sized_array_get_child (GVariantSerialised value,
+ gsize index_)
+@@ -708,6 +739,49 @@ gvs_variable_sized_array_get_child (GVariantSerialised value,
+ g_variant_type_info_ref (child.type_info);
+ child.depth = value.depth + 1;
+
++ /* If the requested @index_ is beyond the set of indices whose framing offsets
++ * have been checked, check the remaining offsets to see whether they’re
++ * normal (in order, no overlapping array elements). */
++ if (index_ > value.ordered_offsets_up_to)
++ {
++ switch (offsets.offset_size)
++ {
++ case 1:
++ {
++ value.ordered_offsets_up_to = find_unordered_guint8 (
++ offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ break;
++ }
++ case 2:
++ {
++ value.ordered_offsets_up_to = find_unordered_guint16 (
++ offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ break;
++ }
++ case 4:
++ {
++ value.ordered_offsets_up_to = find_unordered_guint32 (
++ offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ break;
++ }
++ case 8:
++ {
++ value.ordered_offsets_up_to = find_unordered_guint64 (
++ offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ break;
++ }
++ default:
++ /* gvs_get_offset_size() only returns maximum 8 */
++ g_assert_not_reached ();
++ }
++ }
++
++ if (index_ > value.ordered_offsets_up_to)
++ {
++ /* Offsets are invalid somewhere, so return an empty child. */
++ return child;
++ }
++
+ if (index_ > 0)
+ {
+ guint alignment;
+@@ -842,6 +916,9 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+
+ g_assert (offset == offsets.data_size);
+
++ /* All offsets have now been checked. */
++ value.ordered_offsets_up_to = G_MAXSIZE;
++
+ return TRUE;
+ }
+
+@@ -1074,7 +1151,7 @@ gvs_tuple_is_normal (GVariantSerialised value)
+ for (i = 0; i < length; i++)
+ {
+ const GVariantMemberInfo *member_info;
+- GVariantSerialised child;
++ GVariantSerialised child = { 0, };
+ gsize fixed_size;
+ guint alignment;
+ gsize end;
+@@ -1134,6 +1211,9 @@ gvs_tuple_is_normal (GVariantSerialised value)
+ offset = end;
+ }
+
++ /* All element bounds have been checked above. */
++ value.ordered_offsets_up_to = G_MAXSIZE;
++
+ {
+ gsize fixed_size;
+ guint alignment;
+diff --git a/glib/gvariant-serialiser.h b/glib/gvariant-serialiser.h
+index 6ced7e3d6c..a1bccb834b 100644
+--- a/glib/gvariant-serialiser.h
++++ b/glib/gvariant-serialiser.h
+@@ -31,6 +31,15 @@ typedef struct
+ guchar *data;
+ gsize size;
+ gsize depth; /* same semantics as GVariant.depth */
++
++ /* If ordered_offsets_up_to == n this means that all the frame offsets up to and
++ * including the frame offset determining the end of element n are in order.
++ * This guarantees that the bytes of element n don't overlap with any previous
++ * element.
++ *
++ * This is both read and set by g_variant_serialised_get_child for arrays of
++ * non-fixed-width types */
++ gsize ordered_offsets_up_to;
+ } GVariantSerialised;
+
+ /* deserialization */
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 1a57dc6ef7..c759e8d701 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -1,5 +1,6 @@
+ /*
+ * Copyright © 2010 Codethink Limited
++ * Copyright © 2020 William Manley
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+@@ -1281,6 +1282,7 @@ random_instance_filler (GVariantSerialised *serialised,
+ serialised->size = instance->size;
+
+ serialised->depth = 0;
++ serialised->ordered_offsets_up_to = 0;
+
+ g_assert_true (serialised->type_info == instance->type_info);
+ g_assert_cmpuint (serialised->size, ==, instance->size);
+@@ -5131,6 +5133,47 @@ test_normal_checking_array_offsets (void)
+ g_variant_unref (variant);
+ }
+
++/* This is a regression test that we can't have non-normal values that take up
++ * significantly more space than the normal equivalent, by specifying the
++ * offset table entries so that array elements overlap.
++ *
++ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_832242 */
++static void
++test_normal_checking_array_offsets2 (void)
++{
++ const guint8 data[] = {
++ 'h', 'i', '\0',
++ 0x03, 0x00, 0x03,
++ 0x06, 0x00, 0x06,
++ 0x09, 0x00, 0x09,
++ 0x0c, 0x00, 0x0c,
++ 0x0f, 0x00, 0x0f,
++ 0x12, 0x00, 0x12,
++ 0x15, 0x00, 0x15,
++ };
++ gsize size = sizeof (data);
++ const GVariantType *aaaaaaas = G_VARIANT_TYPE ("aaaaaaas");
++ GVariant *variant = NULL;
++ GVariant *normal_variant = NULL;
++ GVariant *expected = NULL;
++
++ variant = g_variant_new_from_data (aaaaaaas, data, size, FALSE, NULL, NULL);
++ g_assert_nonnull (variant);
++
++ normal_variant = g_variant_get_normal_form (variant);
++ g_assert_nonnull (normal_variant);
++ g_assert_cmpuint (g_variant_get_size (normal_variant), <=, size * 2);
++
++ expected = g_variant_new_parsed (
++ "[[[[[[['hi', '', ''], [], []], [], []], [], []], [], []], [], []], [], []]");
++ g_assert_cmpvariant (expected, variant);
++ g_assert_cmpvariant (expected, normal_variant);
++
++ g_variant_unref (expected);
++ g_variant_unref (normal_variant);
++ g_variant_unref (variant);
++}
++
+ /* Test that a tuple with invalidly large values in its offset table is
+ * normalised successfully without looping infinitely. */
+ static void
+@@ -5299,6 +5342,8 @@ main (int argc, char **argv)
+ test_normal_checking_tuples);
+ g_test_add_func ("/gvariant/normal-checking/array-offsets",
+ test_normal_checking_array_offsets);
++ g_test_add_func ("/gvariant/normal-checking/array-offsets2",
++ test_normal_checking_array_offsets2);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets",
+ test_normal_checking_tuple_offsets);
+ g_test_add_func ("/gvariant/normal-checking/empty-object-path",
+--
+GitLab
+
+
+From 66e7c10aa1ec3102c49186f83671a5f0461ffbc0 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Fri, 7 Jan 2022 15:03:52 +0000
+Subject: [PATCH 05/18] gvariant-serialiser: Factor out code to get bounds of a
+ tuple member
+
+This introduces no functional changes.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2121
+---
+ glib/gvariant-serialiser.c | 73 ++++++++++++++++++++++++--------------
+ 1 file changed, 46 insertions(+), 27 deletions(-)
+
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index 1aa2b8fa9b..a518117e3c 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -944,6 +944,51 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+ * for the tuple. See the notes in gvarianttypeinfo.h.
+ */
+
++static void
++gvs_tuple_get_member_bounds (GVariantSerialised value,
++ gsize index_,
++ gsize offset_size,
++ gsize *out_member_start,
++ gsize *out_member_end)
++{
++ const GVariantMemberInfo *member_info;
++ gsize member_start, member_end;
++
++ member_info = g_variant_type_info_member_info (value.type_info, index_);
++
++ if (member_info->i + 1)
++ member_start = gvs_read_unaligned_le (value.data + value.size -
++ offset_size * (member_info->i + 1),
++ offset_size);
++ else
++ member_start = 0;
++
++ member_start += member_info->a;
++ member_start &= member_info->b;
++ member_start |= member_info->c;
++
++ if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_LAST)
++ member_end = value.size - offset_size * (member_info->i + 1);
++
++ else if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_FIXED)
++ {
++ gsize fixed_size;
++
++ g_variant_type_info_query (member_info->type_info, NULL, &fixed_size);
++ member_end = member_start + fixed_size;
++ }
++
++ else /* G_VARIANT_MEMBER_ENDING_OFFSET */
++ member_end = gvs_read_unaligned_le (value.data + value.size -
++ offset_size * (member_info->i + 2),
++ offset_size);
++
++ if (out_member_start != NULL)
++ *out_member_start = member_start;
++ if (out_member_end != NULL)
++ *out_member_end = member_end;
++}
++
+ static gsize
+ gvs_tuple_n_children (GVariantSerialised value)
+ {
+@@ -999,33 +1044,7 @@ gvs_tuple_get_child (GVariantSerialised value,
+ }
+ }
+
+- if (member_info->i + 1)
+- start = gvs_read_unaligned_le (value.data + value.size -
+- offset_size * (member_info->i + 1),
+- offset_size);
+- else
+- start = 0;
+-
+- start += member_info->a;
+- start &= member_info->b;
+- start |= member_info->c;
+-
+- if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_LAST)
+- end = value.size - offset_size * (member_info->i + 1);
+-
+- else if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_FIXED)
+- {
+- gsize fixed_size;
+-
+- g_variant_type_info_query (child.type_info, NULL, &fixed_size);
+- end = start + fixed_size;
+- child.size = fixed_size;
+- }
+-
+- else /* G_VARIANT_MEMBER_ENDING_OFFSET */
+- end = gvs_read_unaligned_le (value.data + value.size -
+- offset_size * (member_info->i + 2),
+- offset_size);
++ gvs_tuple_get_member_bounds (value, index_, offset_size, &start, &end);
+
+ /* The child should not extend into the offset table. */
+ if (index_ != g_variant_type_info_n_members (value.type_info) - 1)
+--
+GitLab
+
+
+From a62a6b5d3e53b30a4628db2a077ab9ed03605748 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Fri, 7 Jan 2022 16:37:29 +0000
+Subject: [PATCH 06/18] gvariant-serialiser: Rework child size calculation
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This reduces a few duplicate calls to `g_variant_type_info_query()` and
+explains why they’re needed.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2121
+---
+ glib/gvariant-serialiser.c | 31 +++++++++----------------------
+ 1 file changed, 9 insertions(+), 22 deletions(-)
+
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index a518117e3c..d32e255480 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -1009,14 +1009,18 @@ gvs_tuple_get_child (GVariantSerialised value,
+ child.depth = value.depth + 1;
+ offset_size = gvs_get_offset_size (value.size);
+
++ /* Ensure the size is set for fixed-sized children, or
++ * g_variant_serialised_check() will fail, even if we return
++ * (child.data == NULL) to indicate an error. */
++ if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_FIXED)
++ g_variant_type_info_query (child.type_info, NULL, &child.size);
++
+ /* tuples are the only (potentially) fixed-sized containers, so the
+ * only ones that have to deal with the possibility of having %NULL
+ * data with a non-zero %size if errors occurred elsewhere.
+ */
+ if G_UNLIKELY (value.data == NULL && value.size != 0)
+ {
+- g_variant_type_info_query (child.type_info, NULL, &child.size);
+-
+ /* this can only happen in fixed-sized tuples,
+ * so the child must also be fixed sized.
+ */
+@@ -1034,29 +1038,12 @@ gvs_tuple_get_child (GVariantSerialised value,
+ else
+ {
+ if (offset_size * (member_info->i + 1) > value.size)
+- {
+- /* if the child is fixed size, return its size.
+- * if child is not fixed-sized, return size = 0.
+- */
+- g_variant_type_info_query (child.type_info, NULL, &child.size);
+-
+- return child;
+- }
++ return child;
+ }
+
+- gvs_tuple_get_member_bounds (value, index_, offset_size, &start, &end);
+-
+ /* The child should not extend into the offset table. */
+- if (index_ != g_variant_type_info_n_members (value.type_info) - 1)
+- {
+- GVariantSerialised last_child;
+- last_child = gvs_tuple_get_child (value,
+- g_variant_type_info_n_members (value.type_info) - 1);
+- last_end = last_child.data + last_child.size - value.data;
+- g_variant_type_info_unref (last_child.type_info);
+- }
+- else
+- last_end = end;
++ gvs_tuple_get_member_bounds (value, index_, offset_size, &start, &end);
++ gvs_tuple_get_member_bounds (value, g_variant_type_info_n_members (value.type_info) - 1, offset_size, NULL, &last_end);
+
+ if (start < end && end <= value.size && end <= last_end)
+ {
+--
+GitLab
+
+
+From 2d55b3b74b1bc256e91a9d0d59120570376e6acc Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Fri, 7 Jan 2022 16:42:14 +0000
+Subject: [PATCH 07/18] =?UTF-8?q?gvariant:=20Don=E2=80=99t=20allow=20child?=
+ =?UTF-8?q?=20elements=20of=20a=20tuple=20to=20overlap=20each=20other?=
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This is similar to the earlier commit which prevents child elements of a
+variable-sized array from overlapping each other, but this time for
+tuples. It is based heavily on ideas by William Manley.
+
+Tuples are slightly different from variable-sized arrays in that they
+contain a mixture of fixed and variable sized elements. All but one of
+the variable sized elements have an entry in the frame offsets table.
+This means that if we were to just check the ordering of the frame
+offsets table, the variable sized elements could still overlap
+interleaving fixed sized elements, which would be bad.
+
+Therefore we have to check the elements rather than the frame offsets.
+
+The logic of checking the elements up to the index currently being
+requested, and caching the result in `ordered_offsets_up_to`, means that
+the algorithmic cost implications are the same for this commit as for
+variable-sized arrays: an O(N) cost for these checks is amortised out
+over N accesses to O(1) per access.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Fixes: #2121
+---
+ glib/gvariant-core.c | 6 +-
+ glib/gvariant-serialiser.c | 40 ++++++++
+ glib/gvariant-serialiser.h | 7 +-
+ glib/gvariant.c | 1 +
+ glib/tests/gvariant.c | 181 +++++++++++++++++++++++++++++++++++++
+ 5 files changed, 232 insertions(+), 3 deletions(-)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index a75e7db9ea..c0e1931c39 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -1,6 +1,7 @@
+ /*
+ * Copyright © 2007, 2008 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
++ * Copyright © 2022 Endless OS Foundation, LLC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+@@ -181,7 +182,7 @@ struct _GVariant
+ * offsets themselves.
+ *
+ * This field is only relevant for arrays of non
+- * fixed width types.
++ * fixed width types and for tuples.
+ *
+ * .tree: Only valid when the instance is in tree form.
+ *
+@@ -1141,6 +1142,9 @@ g_variant_get_child_value (GVariant *value,
+ */
+ s_child = g_variant_serialised_get_child (serialised, index_);
+
++ /* Update the cached ordered_offsets_up_to, since @serialised will be thrown away when this function exits */
++ value->contents.serialised.ordered_offsets_up_to = MAX (value->contents.serialised.ordered_offsets_up_to, serialised.ordered_offsets_up_to);
++
+ /* Check whether this would cause nesting too deep. If so, return a fake
+ * child. The only situation we expect this to happen in is with a variant,
+ * as all other deeply-nested types have a static type, and hence should
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index d32e255480..fda59d6843 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -944,6 +944,10 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+ * for the tuple. See the notes in gvarianttypeinfo.h.
+ */
+
++/* Note: This doesn’t guarantee that @out_member_end >= @out_member_start; that
++ * condition may not hold true for invalid serialised variants. The caller is
++ * responsible for checking the returned values and handling invalid ones
++ * appropriately. */
+ static void
+ gvs_tuple_get_member_bounds (GVariantSerialised value,
+ gsize index_,
+@@ -1030,6 +1034,42 @@ gvs_tuple_get_child (GVariantSerialised value,
+ return child;
+ }
+
++ /* If the requested @index_ is beyond the set of indices whose framing offsets
++ * have been checked, check the remaining offsets to see whether they’re
++ * normal (in order, no overlapping tuple elements).
++ *
++ * Unlike the checks in gvs_variable_sized_array_get_child(), we have to check
++ * all the tuple *elements* here, not just all the framing offsets, since
++ * tuples contain a mix of elements which use framing offsets and ones which
++ * don’t. None of them are allowed to overlap. */
++ if (index_ > value.ordered_offsets_up_to)
++ {
++ gsize i, prev_i_end = 0;
++
++ if (value.ordered_offsets_up_to > 0)
++ gvs_tuple_get_member_bounds (value, value.ordered_offsets_up_to - 1, offset_size, NULL, &prev_i_end);
++
++ for (i = value.ordered_offsets_up_to; i <= index_; i++)
++ {
++ gsize i_start, i_end;
++
++ gvs_tuple_get_member_bounds (value, i, offset_size, &i_start, &i_end);
++
++ if (i_start > i_end || i_start < prev_i_end || i_end > value.size)
++ break;
++
++ prev_i_end = i_end;
++ }
++
++ value.ordered_offsets_up_to = i - 1;
++ }
++
++ if (index_ > value.ordered_offsets_up_to)
++ {
++ /* Offsets are invalid somewhere, so return an empty child. */
++ return child;
++ }
++
+ if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_OFFSET)
+ {
+ if (offset_size * (member_info->i + 2) > value.size)
+diff --git a/glib/gvariant-serialiser.h b/glib/gvariant-serialiser.h
+index a1bccb834b..661bd8fd1b 100644
+--- a/glib/gvariant-serialiser.h
++++ b/glib/gvariant-serialiser.h
+@@ -37,8 +37,11 @@ typedef struct
+ * This guarantees that the bytes of element n don't overlap with any previous
+ * element.
+ *
+- * This is both read and set by g_variant_serialised_get_child for arrays of
+- * non-fixed-width types */
++ * This is both read and set by g_variant_serialised_get_child() for arrays of
++ * non-fixed-width types, and for tuples.
++ *
++ * Even when dealing with tuples, @ordered_offsets_up_to is an element index,
++ * rather than an index into the frame offsets. */
+ gsize ordered_offsets_up_to;
+ } GVariantSerialised;
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index 6912eebe05..16af7a5678 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -6010,6 +6010,7 @@ g_variant_byteswap (GVariant *value)
+ serialised.size = g_variant_get_size (trusted);
+ serialised.data = g_malloc (serialised.size);
+ serialised.depth = g_variant_get_depth (trusted);
++ serialised.ordered_offsets_up_to = G_MAXSIZE; /* operating on the normal form */
+ g_variant_store (trusted, serialised.data);
+ g_variant_unref (trusted);
+
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index c759e8d701..0cb69d4b70 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -1,6 +1,7 @@
+ /*
+ * Copyright © 2010 Codethink Limited
+ * Copyright © 2020 William Manley
++ * Copyright © 2022 Endless OS Foundation, LLC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+@@ -1449,6 +1450,7 @@ test_maybe (void)
+ serialised.data = flavoured_malloc (needed_size, flavour);
+ serialised.size = needed_size;
+ serialised.depth = 0;
++ serialised.ordered_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised,
+ random_instance_filler,
+@@ -1572,6 +1574,7 @@ test_array (void)
+ serialised.data = flavoured_malloc (needed_size, flavour);
+ serialised.size = needed_size;
+ serialised.depth = 0;
++ serialised.ordered_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised, random_instance_filler,
+ (gpointer *) instances, n_children);
+@@ -1736,6 +1739,7 @@ test_tuple (void)
+ serialised.data = flavoured_malloc (needed_size, flavour);
+ serialised.size = needed_size;
+ serialised.depth = 0;
++ serialised.ordered_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised, random_instance_filler,
+ (gpointer *) instances, n_children);
+@@ -1832,6 +1836,7 @@ test_variant (void)
+ serialised.data = flavoured_malloc (needed_size, flavour);
+ serialised.size = needed_size;
+ serialised.depth = 0;
++ serialised.ordered_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised, random_instance_filler,
+ (gpointer *) &instance, 1);
+@@ -5198,6 +5203,176 @@ test_normal_checking_tuple_offsets (void)
+ g_variant_unref (variant);
+ }
+
++/* This is a regression test that we can't have non-normal values that take up
++ * significantly more space than the normal equivalent, by specifying the
++ * offset table entries so that tuple elements overlap.
++ *
++ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_838503 and
++ * https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_838513 */
++static void
++test_normal_checking_tuple_offsets2 (void)
++{
++ const GVariantType *data_type = G_VARIANT_TYPE ("(yyaiyyaiyy)");
++ const guint8 data[] = {
++ 0x12, 0x34, 0x56, 0x78, 0x01,
++ /*
++ ^───────────────────┘
++
++ ^^^^^^^^^^ 1st yy
++ ^^^^^^^^^^ 2nd yy
++ ^^^^^^^^^^ 3rd yy
++ ^^^^ Framing offsets
++ */
++
++ /* If this variant was encoded normally, it would be something like this:
++ * 0x12, 0x34, pad, pad, [array bytes], 0x56, 0x78, pad, pad, [array bytes], 0x9A, 0xBC, 0xXX
++ * ^─────────────────────────────────────────────────────┘
++ *
++ * ^^^^^^^^^^ 1st yy
++ * ^^^^^^^^^^ 2nd yy
++ * ^^^^^^^^^^ 3rd yy
++ * ^^^^ Framing offsets
++ */
++ };
++ gsize size = sizeof (data);
++ GVariant *variant = NULL;
++ GVariant *normal_variant = NULL;
++ GVariant *expected = NULL;
++
++ variant = g_variant_new_from_data (data_type, data, size, FALSE, NULL, NULL);
++ g_assert_nonnull (variant);
++
++ normal_variant = g_variant_get_normal_form (variant);
++ g_assert_nonnull (normal_variant);
++ g_assert_cmpuint (g_variant_get_size (normal_variant), <=, size * 3);
++
++ expected = g_variant_new_parsed (
++ "@(yyaiyyaiyy) (0x12, 0x34, [], 0x00, 0x00, [], 0x00, 0x00)");
++ g_assert_cmpvariant (expected, variant);
++ g_assert_cmpvariant (expected, normal_variant);
++
++ g_variant_unref (expected);
++ g_variant_unref (normal_variant);
++ g_variant_unref (variant);
++}
++
++/* This is a regression test that overlapping entries in the offset table are
++ * decoded consistently, even though they’re non-normal.
++ *
++ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_910935 */
++static void
++test_normal_checking_tuple_offsets3 (void)
++{
++ /* The expected decoding of this non-normal byte stream is complex. See
++ * section 2.7.3 (Handling Non-Normal Serialised Data) of the GVariant
++ * specification.
++ *
++ * The rule “Child Values Overlapping Framing Offsets” from the specification
++ * says that the first `ay` must be decoded as `[0x01]` even though it
++ * overlaps the first byte of the offset table. However, since commit
++ * 7eedcd76f7d5b8c98fa60013e1fe6e960bf19df3, GLib explicitly doesn’t allow
++ * this as it’s exploitable. So the first `ay` must be given a default value.
++ *
++ * The second and third `ay`s must be given default values because of rule
++ * “End Boundary Precedes Start Boundary”.
++ *
++ * The `i` must be given a default value because of rule “Start or End
++ * Boundary of a Child Falls Outside the Container”.
++ */
++ const GVariantType *data_type = G_VARIANT_TYPE ("(ayayiay)");
++ const guint8 data[] = {
++ 0x01, 0x00, 0x02,
++ /*
++ ^──┘
++
++ ^^^^^^^^^^ 1st ay, bytes 0-2 (but given a default value anyway, see above)
++ 2nd ay, bytes 2-0
++ i, bytes 0-4
++ 3rd ay, bytes 4-1
++ ^^^^^^^^^^ Framing offsets
++ */
++ };
++ gsize size = sizeof (data);
++ GVariant *variant = NULL;
++ GVariant *normal_variant = NULL;
++ GVariant *expected = NULL;
++
++ variant = g_variant_new_from_data (data_type, data, size, FALSE, NULL, NULL);
++ g_assert_nonnull (variant);
++
++ g_assert_false (g_variant_is_normal_form (variant));
++
++ normal_variant = g_variant_get_normal_form (variant);
++ g_assert_nonnull (normal_variant);
++ g_assert_cmpuint (g_variant_get_size (normal_variant), <=, size * 3);
++
++ expected = g_variant_new_parsed ("@(ayayiay) ([], [], 0, [])");
++ g_assert_cmpvariant (expected, variant);
++ g_assert_cmpvariant (expected, normal_variant);
++
++ g_variant_unref (expected);
++ g_variant_unref (normal_variant);
++ g_variant_unref (variant);
++}
++
++/* This is a regression test that overlapping entries in the offset table are
++ * decoded consistently, even though they’re non-normal.
++ *
++ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_910935 */
++static void
++test_normal_checking_tuple_offsets4 (void)
++{
++ /* The expected decoding of this non-normal byte stream is complex. See
++ * section 2.7.3 (Handling Non-Normal Serialised Data) of the GVariant
++ * specification.
++ *
++ * The rule “Child Values Overlapping Framing Offsets” from the specification
++ * says that the first `ay` must be decoded as `[0x01]` even though it
++ * overlaps the first byte of the offset table. However, since commit
++ * 7eedcd76f7d5b8c98fa60013e1fe6e960bf19df3, GLib explicitly doesn’t allow
++ * this as it’s exploitable. So the first `ay` must be given a default value.
++ *
++ * The second `ay` must be given a default value because of rule “End Boundary
++ * Precedes Start Boundary”.
++ *
++ * The third `ay` must be given a default value because its framing offsets
++ * overlap that of the first `ay`.
++ */
++ const GVariantType *data_type = G_VARIANT_TYPE ("(ayayay)");
++ const guint8 data[] = {
++ 0x01, 0x00, 0x02,
++ /*
++ ^──┘
++
++ ^^^^^^^^^^ 1st ay, bytes 0-2 (but given a default value anyway, see above)
++ 2nd ay, bytes 2-0
++ 3rd ay, bytes 0-1
++ ^^^^^^^^^^ Framing offsets
++ */
++ };
++ gsize size = sizeof (data);
++ GVariant *variant = NULL;
++ GVariant *normal_variant = NULL;
++ GVariant *expected = NULL;
++
++ variant = g_variant_new_from_data (data_type, data, size, FALSE, NULL, NULL);
++ g_assert_nonnull (variant);
++
++ g_assert_false (g_variant_is_normal_form (variant));
++
++ normal_variant = g_variant_get_normal_form (variant);
++ g_assert_nonnull (normal_variant);
++ g_assert_cmpuint (g_variant_get_size (normal_variant), <=, size * 3);
++
++ expected = g_variant_new_parsed ("@(ayayay) ([], [], [])");
++ g_assert_cmpvariant (expected, variant);
++ g_assert_cmpvariant (expected, normal_variant);
++
++ g_variant_unref (expected);
++ g_variant_unref (normal_variant);
++ g_variant_unref (variant);
++}
++
+ /* Test that an empty object path is normalised successfully to the base object
+ * path, ‘/’. */
+ static void
+@@ -5346,6 +5521,12 @@ main (int argc, char **argv)
+ test_normal_checking_array_offsets2);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets",
+ test_normal_checking_tuple_offsets);
++ g_test_add_func ("/gvariant/normal-checking/tuple-offsets2",
++ test_normal_checking_tuple_offsets2);
++ g_test_add_func ("/gvariant/normal-checking/tuple-offsets3",
++ test_normal_checking_tuple_offsets3);
++ g_test_add_func ("/gvariant/normal-checking/tuple-offsets4",
++ test_normal_checking_tuple_offsets4);
+ g_test_add_func ("/gvariant/normal-checking/empty-object-path",
+ test_normal_checking_empty_object_path);
+
+--
+GitLab
+
+
+From a6cb880af0a0932493ba096f01990e694e2c5b72 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Tue, 25 Oct 2022 15:14:14 +0100
+Subject: [PATCH 08/18] gvariant: Track checked and ordered offsets
+ independently
+
+The past few commits introduced the concept of known-good offsets in the
+offset table (which is used for variable-width arrays and tuples).
+Good offsets are ones which are non-overlapping with all the previous
+offsets in the table.
+
+If a bad offset is encountered when indexing into the array or tuple,
+the cached known-good offset index will not be increased. In this way,
+all child variants at and beyond the first bad offset can be returned as
+default values rather than dereferencing potentially invalid data.
+
+In this case, there was no information about the fact that the indexes
+between the highest known-good index and the requested one had been
+checked already. That could lead to a pathological case where an offset
+table with an invalid first offset is repeatedly checked in full when
+trying to access higher-indexed children.
+
+Avoid that by storing the index of the highest checked offset in the
+table, as well as the index of the highest good/ordered offset.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2121
+---
+ glib/gvariant-core.c | 28 ++++++++++++++++++++++++
+ glib/gvariant-serialiser.c | 44 +++++++++++++++++++++++++++-----------
+ glib/gvariant-serialiser.h | 9 ++++++++
+ glib/gvariant.c | 1 +
+ glib/tests/gvariant.c | 5 +++++
+ 5 files changed, 75 insertions(+), 12 deletions(-)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index c0e1931c39..e61242f763 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -69,6 +69,7 @@ struct _GVariant
+ GBytes *bytes;
+ gconstpointer data;
+ gsize ordered_offsets_up_to;
++ gsize checked_offsets_up_to;
+ } serialised;
+
+ struct
+@@ -184,6 +185,24 @@ struct _GVariant
+ * This field is only relevant for arrays of non
+ * fixed width types and for tuples.
+ *
++ * .checked_offsets_up_to: Similarly to .ordered_offsets_up_to, this stores
++ * the index of the highest element, n, whose frame
++ * offsets (and all the preceding frame offsets)
++ * have been checked for validity.
++ *
++ * It is always the case that
++ * .checked_offsets_up_to ≥ .ordered_offsets_up_to.
++ *
++ * If .checked_offsets_up_to == .ordered_offsets_up_to,
++ * then a bad offset has not been found so far.
++ *
++ * If .checked_offsets_up_to > .ordered_offsets_up_to,
++ * then a bad offset has been found at
++ * (.ordered_offsets_up_to + 1).
++ *
++ * This field is only relevant for arrays of non
++ * fixed width types and for tuples.
++ *
+ * .tree: Only valid when the instance is in tree form.
+ *
+ * Note that accesses from other threads could result in
+@@ -388,6 +407,7 @@ g_variant_to_serialised (GVariant *value)
+ value->size,
+ value->depth,
+ value->contents.serialised.ordered_offsets_up_to,
++ value->contents.serialised.checked_offsets_up_to,
+ };
+ return serialised;
+ }
+@@ -420,6 +440,7 @@ g_variant_serialise (GVariant *value,
+ serialised.data = data;
+ serialised.depth = value->depth;
+ serialised.ordered_offsets_up_to = 0;
++ serialised.checked_offsets_up_to = 0;
+
+ children = (gpointer *) value->contents.tree.children;
+ n_children = value->contents.tree.n_children;
+@@ -466,10 +487,12 @@ g_variant_fill_gvs (GVariantSerialised *serialised,
+ if (value->state & STATE_SERIALISED)
+ {
+ serialised->ordered_offsets_up_to = value->contents.serialised.ordered_offsets_up_to;
++ serialised->checked_offsets_up_to = value->contents.serialised.checked_offsets_up_to;
+ }
+ else
+ {
+ serialised->ordered_offsets_up_to = 0;
++ serialised->checked_offsets_up_to = 0;
+ }
+
+ if (serialised->data)
+@@ -515,6 +538,7 @@ g_variant_ensure_serialised (GVariant *value)
+ value->contents.serialised.data = g_bytes_get_data (bytes, NULL);
+ value->contents.serialised.bytes = bytes;
+ value->contents.serialised.ordered_offsets_up_to = G_MAXSIZE;
++ value->contents.serialised.checked_offsets_up_to = G_MAXSIZE;
+ value->state |= STATE_SERIALISED;
+ }
+ }
+@@ -596,6 +620,7 @@ g_variant_new_from_bytes (const GVariantType *type,
+ serialised.data = (guchar *) g_bytes_get_data (bytes, &serialised.size);
+ serialised.depth = 0;
+ serialised.ordered_offsets_up_to = trusted ? G_MAXSIZE : 0;
++ serialised.checked_offsets_up_to = trusted ? G_MAXSIZE : 0;
+
+ if (!g_variant_serialised_check (serialised))
+ {
+@@ -647,6 +672,7 @@ g_variant_new_from_bytes (const GVariantType *type,
+ }
+
+ value->contents.serialised.ordered_offsets_up_to = trusted ? G_MAXSIZE : 0;
++ value->contents.serialised.checked_offsets_up_to = trusted ? G_MAXSIZE : 0;
+
+ g_clear_pointer (&owned_bytes, g_bytes_unref);
+
+@@ -1144,6 +1170,7 @@ g_variant_get_child_value (GVariant *value,
+
+ /* Update the cached ordered_offsets_up_to, since @serialised will be thrown away when this function exits */
+ value->contents.serialised.ordered_offsets_up_to = MAX (value->contents.serialised.ordered_offsets_up_to, serialised.ordered_offsets_up_to);
++ value->contents.serialised.checked_offsets_up_to = MAX (value->contents.serialised.checked_offsets_up_to, serialised.checked_offsets_up_to);
+
+ /* Check whether this would cause nesting too deep. If so, return a fake
+ * child. The only situation we expect this to happen in is with a variant,
+@@ -1171,6 +1198,7 @@ g_variant_get_child_value (GVariant *value,
+ g_bytes_ref (value->contents.serialised.bytes);
+ child->contents.serialised.data = s_child.data;
+ child->contents.serialised.ordered_offsets_up_to = s_child.ordered_offsets_up_to;
++ child->contents.serialised.checked_offsets_up_to = s_child.checked_offsets_up_to;
+
+ return child;
+ }
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index fda59d6843..591522a2e1 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -122,6 +122,8 @@
+ *
+ * @depth has no restrictions; the depth of a top-level serialized #GVariant is
+ * zero, and it increases for each level of nested child.
++ *
++ * @checked_offsets_up_to is always ≥ @ordered_offsets_up_to
+ */
+
+ /* < private >
+@@ -149,6 +151,9 @@ g_variant_serialised_check (GVariantSerialised serialised)
+ !(serialised.size == 0 || serialised.data != NULL))
+ return FALSE;
+
++ if (serialised.ordered_offsets_up_to > serialised.checked_offsets_up_to)
++ return FALSE;
++
+ /* Depending on the native alignment requirements of the machine, the
+ * compiler will insert either 3 or 7 padding bytes after the char.
+ * This will result in the sizeof() the struct being 12 or 16.
+@@ -268,6 +273,7 @@ gvs_fixed_sized_maybe_get_child (GVariantSerialised value,
+ g_variant_type_info_ref (value.type_info);
+ value.depth++;
+ value.ordered_offsets_up_to = 0;
++ value.checked_offsets_up_to = 0;
+
+ return value;
+ }
+@@ -299,7 +305,7 @@ gvs_fixed_sized_maybe_serialise (GVariantSerialised value,
+ {
+ if (n_children)
+ {
+- GVariantSerialised child = { NULL, value.data, value.size, value.depth + 1, 0 };
++ GVariantSerialised child = { NULL, value.data, value.size, value.depth + 1, 0, 0 };
+
+ gvs_filler (&child, children[0]);
+ }
+@@ -322,6 +328,7 @@ gvs_fixed_sized_maybe_is_normal (GVariantSerialised value)
+ value.type_info = g_variant_type_info_element (value.type_info);
+ value.depth++;
+ value.ordered_offsets_up_to = 0;
++ value.checked_offsets_up_to = 0;
+
+ return g_variant_serialised_is_normal (value);
+ }
+@@ -364,6 +371,7 @@ gvs_variable_sized_maybe_get_child (GVariantSerialised value,
+
+ value.depth++;
+ value.ordered_offsets_up_to = 0;
++ value.checked_offsets_up_to = 0;
+
+ return value;
+ }
+@@ -394,7 +402,7 @@ gvs_variable_sized_maybe_serialise (GVariantSerialised value,
+ {
+ if (n_children)
+ {
+- GVariantSerialised child = { NULL, value.data, value.size - 1, value.depth + 1, 0 };
++ GVariantSerialised child = { NULL, value.data, value.size - 1, value.depth + 1, 0, 0 };
+
+ /* write the data for the child. */
+ gvs_filler (&child, children[0]);
+@@ -415,6 +423,7 @@ gvs_variable_sized_maybe_is_normal (GVariantSerialised value)
+ value.size--;
+ value.depth++;
+ value.ordered_offsets_up_to = 0;
++ value.checked_offsets_up_to = 0;
+
+ return g_variant_serialised_is_normal (value);
+ }
+@@ -741,39 +750,46 @@ gvs_variable_sized_array_get_child (GVariantSerialised value,
+
+ /* If the requested @index_ is beyond the set of indices whose framing offsets
+ * have been checked, check the remaining offsets to see whether they’re
+- * normal (in order, no overlapping array elements). */
+- if (index_ > value.ordered_offsets_up_to)
++ * normal (in order, no overlapping array elements).
++ *
++ * Don’t bother checking if the highest known-good offset is lower than the
++ * highest checked offset, as that means there’s an invalid element at that
++ * index, so there’s no need to check further. */
++ if (index_ > value.checked_offsets_up_to &&
++ value.ordered_offsets_up_to == value.checked_offsets_up_to)
+ {
+ switch (offsets.offset_size)
+ {
+ case 1:
+ {
+ value.ordered_offsets_up_to = find_unordered_guint8 (
+- offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ offsets.array, value.checked_offsets_up_to, index_ + 1);
+ break;
+ }
+ case 2:
+ {
+ value.ordered_offsets_up_to = find_unordered_guint16 (
+- offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ offsets.array, value.checked_offsets_up_to, index_ + 1);
+ break;
+ }
+ case 4:
+ {
+ value.ordered_offsets_up_to = find_unordered_guint32 (
+- offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ offsets.array, value.checked_offsets_up_to, index_ + 1);
+ break;
+ }
+ case 8:
+ {
+ value.ordered_offsets_up_to = find_unordered_guint64 (
+- offsets.array, value.ordered_offsets_up_to, index_ + 1);
++ offsets.array, value.checked_offsets_up_to, index_ + 1);
+ break;
+ }
+ default:
+ /* gvs_get_offset_size() only returns maximum 8 */
+ g_assert_not_reached ();
+ }
++
++ value.checked_offsets_up_to = index_;
+ }
+
+ if (index_ > value.ordered_offsets_up_to)
+@@ -918,6 +934,7 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value)
+
+ /* All offsets have now been checked. */
+ value.ordered_offsets_up_to = G_MAXSIZE;
++ value.checked_offsets_up_to = G_MAXSIZE;
+
+ return TRUE;
+ }
+@@ -1042,14 +1059,15 @@ gvs_tuple_get_child (GVariantSerialised value,
+ * all the tuple *elements* here, not just all the framing offsets, since
+ * tuples contain a mix of elements which use framing offsets and ones which
+ * don’t. None of them are allowed to overlap. */
+- if (index_ > value.ordered_offsets_up_to)
++ if (index_ > value.checked_offsets_up_to &&
++ value.ordered_offsets_up_to == value.checked_offsets_up_to)
+ {
+ gsize i, prev_i_end = 0;
+
+- if (value.ordered_offsets_up_to > 0)
+- gvs_tuple_get_member_bounds (value, value.ordered_offsets_up_to - 1, offset_size, NULL, &prev_i_end);
++ if (value.checked_offsets_up_to > 0)
++ gvs_tuple_get_member_bounds (value, value.checked_offsets_up_to - 1, offset_size, NULL, &prev_i_end);
+
+- for (i = value.ordered_offsets_up_to; i <= index_; i++)
++ for (i = value.checked_offsets_up_to; i <= index_; i++)
+ {
+ gsize i_start, i_end;
+
+@@ -1062,6 +1080,7 @@ gvs_tuple_get_child (GVariantSerialised value,
+ }
+
+ value.ordered_offsets_up_to = i - 1;
++ value.checked_offsets_up_to = index_;
+ }
+
+ if (index_ > value.ordered_offsets_up_to)
+@@ -1259,6 +1278,7 @@ gvs_tuple_is_normal (GVariantSerialised value)
+
+ /* All element bounds have been checked above. */
+ value.ordered_offsets_up_to = G_MAXSIZE;
++ value.checked_offsets_up_to = G_MAXSIZE;
+
+ {
+ gsize fixed_size;
+diff --git a/glib/gvariant-serialiser.h b/glib/gvariant-serialiser.h
+index 661bd8fd1b..eb74fe7804 100644
+--- a/glib/gvariant-serialiser.h
++++ b/glib/gvariant-serialiser.h
+@@ -43,6 +43,15 @@ typedef struct
+ * Even when dealing with tuples, @ordered_offsets_up_to is an element index,
+ * rather than an index into the frame offsets. */
+ gsize ordered_offsets_up_to;
++
++ /* Similar to @ordered_offsets_up_to. This gives the index of the child element
++ * whose frame offset is the highest in the offset table which has been
++ * checked so far.
++ *
++ * This is always ≥ @ordered_offsets_up_to. It is always an element index.
++ *
++ * See documentation in gvariant-core.c for `struct GVariant` for details. */
++ gsize checked_offsets_up_to;
+ } GVariantSerialised;
+
+ /* deserialization */
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index 16af7a5678..48e49490ba 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -6011,6 +6011,7 @@ g_variant_byteswap (GVariant *value)
+ serialised.data = g_malloc (serialised.size);
+ serialised.depth = g_variant_get_depth (trusted);
+ serialised.ordered_offsets_up_to = G_MAXSIZE; /* operating on the normal form */
++ serialised.checked_offsets_up_to = G_MAXSIZE;
+ g_variant_store (trusted, serialised.data);
+ g_variant_unref (trusted);
+
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 0cb69d4b70..941e58f1bc 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -1284,6 +1284,7 @@ random_instance_filler (GVariantSerialised *serialised,
+
+ serialised->depth = 0;
+ serialised->ordered_offsets_up_to = 0;
++ serialised->checked_offsets_up_to = 0;
+
+ g_assert_true (serialised->type_info == instance->type_info);
+ g_assert_cmpuint (serialised->size, ==, instance->size);
+@@ -1451,6 +1452,7 @@ test_maybe (void)
+ serialised.size = needed_size;
+ serialised.depth = 0;
+ serialised.ordered_offsets_up_to = 0;
++ serialised.checked_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised,
+ random_instance_filler,
+@@ -1575,6 +1577,7 @@ test_array (void)
+ serialised.size = needed_size;
+ serialised.depth = 0;
+ serialised.ordered_offsets_up_to = 0;
++ serialised.checked_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised, random_instance_filler,
+ (gpointer *) instances, n_children);
+@@ -1740,6 +1743,7 @@ test_tuple (void)
+ serialised.size = needed_size;
+ serialised.depth = 0;
+ serialised.ordered_offsets_up_to = 0;
++ serialised.checked_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised, random_instance_filler,
+ (gpointer *) instances, n_children);
+@@ -1837,6 +1841,7 @@ test_variant (void)
+ serialised.size = needed_size;
+ serialised.depth = 0;
+ serialised.ordered_offsets_up_to = 0;
++ serialised.checked_offsets_up_to = 0;
+
+ g_variant_serialiser_serialise (serialised, random_instance_filler,
+ (gpointer *) &instance, 1);
+--
+GitLab
+
+
+From 8c1a7815e7e6695c120cdedff48395c1222af6d1 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <withnall@endlessm.com>
+Date: Fri, 12 Jun 2020 18:01:13 +0100
+Subject: [PATCH 09/18] tests: Add another test for overlapping offsets in
+ GVariant
+
+Signed-off-by: Philip Withnall <withnall@endlessm.com>
+
+Helps: #2121
+---
+ glib/tests/gvariant.c | 34 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 34 insertions(+)
+
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 941e58f1bc..867cb5b9b4 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -5119,6 +5119,38 @@ test_recursion_limits_array_in_variant (void)
+ g_variant_unref (wrapper_variant);
+ }
+
++/* Test that a nested array with invalid values in its offset table (which point
++ * from the inner to the outer array) is normalised successfully without
++ * looping infinitely. */
++static void
++test_normal_checking_array_offsets_overlapped (void)
++{
++ const guint8 data[] = {
++ 0x01, 0x00,
++ };
++ gsize size = sizeof (data);
++ GVariant *variant = NULL;
++ GVariant *normal_variant = NULL;
++ GVariant *expected_variant = NULL;
++
++ variant = g_variant_new_from_data (G_VARIANT_TYPE ("aay"), data, size,
++ FALSE, NULL, NULL);
++ g_assert_nonnull (variant);
++
++ normal_variant = g_variant_get_normal_form (variant);
++ g_assert_nonnull (normal_variant);
++
++ expected_variant = g_variant_new_parsed ("[@ay [], []]");
++ g_assert_cmpvariant (normal_variant, expected_variant);
++
++ g_assert_cmpmem (g_variant_get_data (normal_variant), g_variant_get_size (normal_variant),
++ g_variant_get_data (expected_variant), g_variant_get_size (expected_variant));
++
++ g_variant_unref (expected_variant);
++ g_variant_unref (normal_variant);
++ g_variant_unref (variant);
++}
++
+ /* Test that an array with invalidly large values in its offset table is
+ * normalised successfully without looping infinitely. */
+ static void
+@@ -5520,6 +5552,8 @@ main (int argc, char **argv)
+
+ g_test_add_func ("/gvariant/normal-checking/tuples",
+ test_normal_checking_tuples);
++ g_test_add_func ("/gvariant/normal-checking/array-offsets/overlapped",
++ test_normal_checking_array_offsets_overlapped);
+ g_test_add_func ("/gvariant/normal-checking/array-offsets",
+ test_normal_checking_array_offsets);
+ g_test_add_func ("/gvariant/normal-checking/array-offsets2",
+--
+GitLab
+
+
+From 019505a7ccc32d0afa06e104dc0ac2e63e6f7189 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Mon, 24 Oct 2022 16:43:23 +0100
+Subject: [PATCH 10/18] tests: Disable some random instance tests of GVariants
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Building a `GVariant` using entirely random data may result in a
+non-normally-formed `GVariant`. It’s always possible to read these
+`GVariant`s, but the API might return default values for some or all of
+their components.
+
+In particular, this can easily happen when randomly generating the
+offset tables for non-fixed-width container types.
+
+If it does happen, bytewise comparison of the parsed `GVariant` with the
+original bytes will not always match. So skip those checks.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2121
+---
+ glib/tests/gvariant.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 867cb5b9b4..9739993e66 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -1231,6 +1231,7 @@ random_instance_assert (RandomInstance *instance,
+ GRand *rand;
+ gsize i;
+
++ g_assert_true (size == 0 || buffer != NULL);
+ g_assert_cmpint ((gsize) buffer & ALIGN_BITS & instance->alignment, ==, 0);
+ g_assert_cmpint (size, ==, instance->size);
+
+@@ -1457,10 +1458,13 @@ test_maybe (void)
+ g_variant_serialiser_serialise (serialised,
+ random_instance_filler,
+ (gpointer *) &instance, 1);
++
+ child = g_variant_serialised_get_child (serialised, 0);
+ g_assert_true (child.type_info == instance->type_info);
+- random_instance_assert (instance, child.data, child.size);
++ if (child.data != NULL) /* could be NULL if element is non-normal */
++ random_instance_assert (instance, child.data, child.size);
+ g_variant_type_info_unref (child.type_info);
++
+ flavoured_free (serialised.data, flavour);
+ }
+ }
+@@ -1593,7 +1597,8 @@ test_array (void)
+
+ child = g_variant_serialised_get_child (serialised, i);
+ g_assert_true (child.type_info == instances[i]->type_info);
+- random_instance_assert (instances[i], child.data, child.size);
++ if (child.data != NULL) /* could be NULL if element is non-normal */
++ random_instance_assert (instances[i], child.data, child.size);
+ g_variant_type_info_unref (child.type_info);
+ }
+
+@@ -1759,7 +1764,8 @@ test_tuple (void)
+
+ child = g_variant_serialised_get_child (serialised, i);
+ g_assert_true (child.type_info == instances[i]->type_info);
+- random_instance_assert (instances[i], child.data, child.size);
++ if (child.data != NULL) /* could be NULL if element is non-normal */
++ random_instance_assert (instances[i], child.data, child.size);
+ g_variant_type_info_unref (child.type_info);
+ }
+
+--
+GitLab
+
+
+From 9d2a142807806212a23436d0332b0209733810f2 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Mon, 24 Oct 2022 18:14:57 +0100
+Subject: [PATCH 11/18] gvariant: Clarify the docs for
+ g_variant_get_normal_form()
+
+Document how non-normal parts of the `GVariant` are handled.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+---
+ glib/gvariant.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index 48e49490ba..e92564eb64 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -5936,7 +5936,9 @@ g_variant_deep_copy (GVariant *value)
+ * marked as trusted and a new reference to it is returned.
+ *
+ * If @value is found not to be in normal form then a new trusted
+- * #GVariant is created with the same value as @value.
++ * #GVariant is created with the same value as @value. The non-normal parts of
++ * @value will be replaced with default values which are guaranteed to be in
++ * normal form.
+ *
+ * It makes sense to call this function if you've received #GVariant
+ * data from untrusted sources and you want to ensure your serialized
+--
+GitLab
+
+
+From b0ccb1616688290088e49dea2dc0d7fe723136e4 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Mon, 24 Oct 2022 18:43:55 +0100
+Subject: [PATCH 12/18] gvariant: Port g_variant_deep_copy() to count its
+ iterations directly
+
+This is equivalent to what `GVariantIter` does, but it means that
+`g_variant_deep_copy()` is making its own `g_variant_get_child_value()`
+calls.
+
+This will be useful in an upcoming commit, where those child values will
+be inspected a little more deeply.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2121
+---
+ glib/gvariant.c | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index e92564eb64..82a921a89e 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -5863,14 +5863,13 @@ g_variant_deep_copy (GVariant *value)
+ case G_VARIANT_CLASS_VARIANT:
+ {
+ GVariantBuilder builder;
+- GVariantIter iter;
+- GVariant *child;
++ gsize i, n_children;
+
+ g_variant_builder_init (&builder, g_variant_get_type (value));
+- g_variant_iter_init (&iter, value);
+
+- while ((child = g_variant_iter_next_value (&iter)))
++ for (i = 0, n_children = g_variant_n_children (value); i < n_children; i++)
+ {
++ GVariant *child = g_variant_get_child_value (value, i);
+ g_variant_builder_add_value (&builder, g_variant_deep_copy (child));
+ g_variant_unref (child);
+ }
+--
+GitLab
+
+
+From 1770e255ae6cc3f0bf5312322432bbc6524a3632 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Tue, 25 Oct 2022 13:03:22 +0100
+Subject: [PATCH 13/18] gvariant: Add internal
+ g_variant_maybe_get_child_value()
+
+This will be used in a following commit.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2540
+---
+ glib/gvariant-core.c | 68 ++++++++++++++++++++++++++++++++++++++++++++
+ glib/gvariant-core.h | 3 ++
+ 2 files changed, 71 insertions(+)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index e61242f763..3f4ca0e6dc 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -1204,6 +1204,74 @@ g_variant_get_child_value (GVariant *value,
+ }
+ }
+
++/**
++ * g_variant_maybe_get_child_value:
++ * @value: a container #GVariant
++ * @index_: the index of the child to fetch
++ *
++ * Reads a child item out of a container #GVariant instance, if it is in normal
++ * form. If it is not in normal form, return %NULL.
++ *
++ * This function behaves the same as g_variant_get_child_value(), except that it
++ * returns %NULL if the child is not in normal form. g_variant_get_child_value()
++ * would instead return a new default value of the correct type.
++ *
++ * This is intended to be used internally to avoid unnecessary #GVariant
++ * allocations.
++ *
++ * The returned value is never floating. You should free it with
++ * g_variant_unref() when you're done with it.
++ *
++ * This function is O(1).
++ *
++ * Returns: (transfer full): the child at the specified index
++ *
++ * Since: 2.74
++ */
++GVariant *
++g_variant_maybe_get_child_value (GVariant *value,
++ gsize index_)
++{
++ g_return_val_if_fail (index_ < g_variant_n_children (value), NULL);
++ g_return_val_if_fail (value->depth < G_MAXSIZE, NULL);
++
++ if (~g_atomic_int_get (&value->state) & STATE_SERIALISED)
++ {
++ g_variant_lock (value);
++
++ if (~value->state & STATE_SERIALISED)
++ {
++ GVariant *child;
++
++ child = g_variant_ref (value->contents.tree.children[index_]);
++ g_variant_unlock (value);
++
++ return child;
++ }
++
++ g_variant_unlock (value);
++ }
++
++ {
++ GVariantSerialised serialised = g_variant_to_serialised (value);
++ GVariantSerialised s_child;
++
++ /* get the serializer to extract the serialized data for the child
++ * from the serialized data for the container
++ */
++ s_child = g_variant_serialised_get_child (serialised, index_);
++
++ if (!(value->state & STATE_TRUSTED) && s_child.data == NULL)
++ {
++ g_variant_type_info_unref (s_child.type_info);
++ return NULL;
++ }
++
++ g_variant_type_info_unref (s_child.type_info);
++ return g_variant_get_child_value (value, index_);
++ }
++}
++
+ /**
+ * g_variant_store:
+ * @value: the #GVariant to store
+diff --git a/glib/gvariant-core.h b/glib/gvariant-core.h
+index a1c34739a3..fb5f4bff45 100644
+--- a/glib/gvariant-core.h
++++ b/glib/gvariant-core.h
+@@ -38,4 +38,7 @@ GVariantTypeInfo * g_variant_get_type_info (GVarian
+
+ gsize g_variant_get_depth (GVariant *value);
+
++GVariant * g_variant_maybe_get_child_value (GVariant *value,
++ gsize index_);
++
+ #endif /* __G_VARIANT_CORE_H__ */
+--
+GitLab
+
+
+From 82fc15af4c84a2645343c51b18ab3528c51790ab Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Tue, 25 Oct 2022 13:03:45 +0100
+Subject: [PATCH 14/18] gvariant: Cut allocs of default values for children of
+ non-normal arrays
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This improves a slow case in `g_variant_get_normal_form()` where
+allocating many identical default values for the children of a
+variable-sized array which has a malformed offset table would take a lot
+of time.
+
+The fix is to make all child values after the first invalid one be
+references to the default value emitted for the first invalid one,
+rather than identical new `GVariant`s.
+
+In particular, this fixes a case where an attacker could create an array
+of length L of very large tuples of size T each, corrupt the offset table
+so they don’t have to specify the array content, and then induce
+`g_variant_get_normal_form()` into allocating L×T default values from an
+input which is significantly smaller than L×T in length.
+
+A pre-existing workaround for this issue is for code to call
+`g_variant_is_normal_form()` before calling
+`g_variant_get_normal_form()`, and to skip the latter call if the former
+returns false. This commit improves the behaviour in the case that
+`g_variant_get_normal_form()` is called anyway.
+
+This fix changes the time to run the `fuzz_variant_binary` test on the
+testcase from oss-fuzz#19777 from >60s (before being terminated) with
+2.3GB of memory usage and 580k page faults; to 32s, 8.3MB of memory
+usage and 1500 page faults (as measured by `time -v`).
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Fixes: #2540
+oss-fuzz#19777
+---
+ glib/gvariant.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 65 insertions(+), 1 deletion(-)
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index 82a921a89e..bb6be363e0 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -5857,7 +5857,6 @@ g_variant_deep_copy (GVariant *value)
+ switch (g_variant_classify (value))
+ {
+ case G_VARIANT_CLASS_MAYBE:
+- case G_VARIANT_CLASS_ARRAY:
+ case G_VARIANT_CLASS_TUPLE:
+ case G_VARIANT_CLASS_DICT_ENTRY:
+ case G_VARIANT_CLASS_VARIANT:
+@@ -5877,6 +5876,71 @@ g_variant_deep_copy (GVariant *value)
+ return g_variant_builder_end (&builder);
+ }
+
++ case G_VARIANT_CLASS_ARRAY:
++ {
++ GVariantBuilder builder;
++ gsize i, n_children;
++ GVariant *first_invalid_child_deep_copy = NULL;
++
++ /* Arrays are in theory treated the same as maybes, tuples, dict entries
++ * and variants, and could be another case in the above block of code.
++ *
++ * However, they have the property that when dealing with non-normal
++ * data (which is the only time g_variant_deep_copy() is currently
++ * called) in a variable-sized array, the code above can easily end up
++ * creating many default child values in order to return an array which
++ * is of the right length and type, but without containing non-normal
++ * data. This can happen if the offset table for the array is malformed.
++ *
++ * In this case, the code above would end up allocating the same default
++ * value for each one of the child indexes beyond the first malformed
++ * entry in the offset table. This can end up being a lot of identical
++ * allocations of default values, particularly if the non-normal array
++ * is crafted maliciously.
++ *
++ * Avoid that problem by returning a new reference to the same default
++ * value for every child after the first invalid one. This results in
++ * returning an equivalent array, in normal form and trusted — but with
++ * significantly fewer memory allocations.
++ *
++ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2540 */
++
++ g_variant_builder_init (&builder, g_variant_get_type (value));
++
++ for (i = 0, n_children = g_variant_n_children (value); i < n_children; i++)
++ {
++ /* Try maybe_get_child_value() first; if it returns NULL, this child
++ * is non-normal. get_child_value() would have constructed and
++ * returned a default value in that case. */
++ GVariant *child = g_variant_maybe_get_child_value (value, i);
++
++ if (child != NULL)
++ {
++ /* Non-normal children may not always be contiguous, as they may
++ * be non-normal for reasons other than invalid offset table
++ * entries. As they are all the same type, they will all have
++ * the same default value though, so keep that around. */
++ g_variant_builder_add_value (&builder, g_variant_deep_copy (child));
++ }
++ else if (child == NULL && first_invalid_child_deep_copy != NULL)
++ {
++ g_variant_builder_add_value (&builder, first_invalid_child_deep_copy);
++ }
++ else if (child == NULL)
++ {
++ child = g_variant_get_child_value (value, i);
++ first_invalid_child_deep_copy = g_variant_ref_sink (g_variant_deep_copy (child));
++ g_variant_builder_add_value (&builder, first_invalid_child_deep_copy);
++ }
++
++ g_clear_pointer (&child, g_variant_unref);
++ }
++
++ g_clear_pointer (&first_invalid_child_deep_copy, g_variant_unref);
++
++ return g_variant_builder_end (&builder);
++ }
++
+ case G_VARIANT_CLASS_BOOLEAN:
+ return g_variant_new_boolean (g_variant_get_boolean (value));
+
+--
+GitLab
+
+
+From 935f1c200789c76ad5b51b1f403f611e3cc75318 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Tue, 25 Oct 2022 18:03:56 +0100
+Subject: [PATCH 15/18] gvariant: Fix a leak of a GVariantTypeInfo on an error
+ handling path
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+---
+ glib/gvariant-core.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
+index 3f4ca0e6dc..75d23f39af 100644
+--- a/glib/gvariant-core.c
++++ b/glib/gvariant-core.c
+@@ -1183,6 +1183,7 @@ g_variant_get_child_value (GVariant *value,
+ G_VARIANT_MAX_RECURSION_DEPTH - value->depth)
+ {
+ g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_VARIANT));
++ g_variant_type_info_unref (s_child.type_info);
+ return g_variant_new_tuple (NULL, 0);
+ }
+
+--
+GitLab
+
+
+From f1dfc5d0c5c0486b5fccb2ceddf1db1162c7033c Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Thu, 27 Oct 2022 12:00:04 +0100
+Subject: [PATCH 16/18] gvariant-serialiser: Check offset table entry size is
+ minimal
+
+The entries in an offset table (which is used for variable sized arrays
+and tuples containing variable sized members) are sized so that they can
+address every byte in the overall variant.
+
+The specification requires that for a variant to be in normal form, its
+offset table entries must be the minimum width such that they can
+address every byte in the variant.
+
+That minimality requirement was not checked in
+`g_variant_is_normal_form()`, leading to two different byte arrays being
+interpreted as the normal form of a given variant tree. That kind of
+confusion could potentially be exploited, and is certainly a bug.
+
+Fix it by adding the necessary checks on offset table entry width, and
+unit tests.
+
+Spotted by William Manley.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Fixes: #2794
+---
+ glib/gvariant-serialiser.c | 19 +++-
+ glib/tests/gvariant.c | 176 +++++++++++++++++++++++++++++++++++++
+ 2 files changed, 194 insertions(+), 1 deletion(-)
+
+diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
+index 591522a2e1..25c85b30b1 100644
+--- a/glib/gvariant-serialiser.c
++++ b/glib/gvariant-serialiser.c
+@@ -696,6 +696,10 @@ gvs_variable_sized_array_get_frame_offsets (GVariantSerialised value)
+ out.data_size = last_end;
+ out.array = value.data + last_end;
+ out.length = offsets_array_size / out.offset_size;
++
++ if (out.length > 0 && gvs_calculate_total_size (last_end, out.length) != value.size)
++ return out; /* offset size not minimal */
++
+ out.is_normal = TRUE;
+
+ return out;
+@@ -1203,6 +1207,7 @@ gvs_tuple_is_normal (GVariantSerialised value)
+ gsize length;
+ gsize offset;
+ gsize i;
++ gsize offset_table_size;
+
+ /* as per the comment in gvs_tuple_get_child() */
+ if G_UNLIKELY (value.data == NULL && value.size != 0)
+@@ -1307,7 +1312,19 @@ gvs_tuple_is_normal (GVariantSerialised value)
+ }
+ }
+
+- return offset_ptr == offset;
++ /* @offset_ptr has been counting backwards from the end of the variant, to
++ * find the beginning of the offset table. @offset has been counting forwards
++ * from the beginning of the variant to find the end of the data. They should
++ * have met in the middle. */
++ if (offset_ptr != offset)
++ return FALSE;
++
++ offset_table_size = value.size - offset_ptr;
++ if (value.size > 0 &&
++ gvs_calculate_total_size (offset, offset_table_size / offset_size) != value.size)
++ return FALSE; /* offset size not minimal */
++
++ return TRUE;
+ }
+
+ /* Variants {{{2
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 9739993e66..339edc4e04 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -5222,6 +5222,86 @@ test_normal_checking_array_offsets2 (void)
+ g_variant_unref (variant);
+ }
+
++/* Test that an otherwise-valid serialised GVariant is considered non-normal if
++ * its offset table entries are too wide.
++ *
++ * See §2.3.6 (Framing Offsets) of the GVariant specification. */
++static void
++test_normal_checking_array_offsets_minimal_sized (void)
++{
++ GVariantBuilder builder;
++ gsize i;
++ GVariant *aay_constructed = NULL;
++ const guint8 *data = NULL;
++ guint8 *data_owned = NULL;
++ GVariant *aay_deserialised = NULL;
++ GVariant *aay_normalised = NULL;
++
++ /* Construct an array of type aay, consisting of 128 elements which are each
++ * an empty array, i.e. `[[] * 128]`. This is chosen because the inner
++ * elements are variable sized (making the outer array variable sized, so it
++ * must have an offset table), but they are also zero-sized when serialised.
++ * So the serialised representation of @aay_constructed consists entirely of
++ * its offset table, which is entirely zeroes.
++ *
++ * The array is chosen to be 128 elements long because that means offset
++ * table entries which are 1 byte long. If the elements in the array were
++ * non-zero-sized (to the extent that the overall array is ≥256 bytes long),
++ * the offset table entries would end up being 2 bytes long. */
++ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aay"));
++
++ for (i = 0; i < 128; i++)
++ g_variant_builder_add_value (&builder, g_variant_new_array (G_VARIANT_TYPE_BYTE, NULL, 0));
++
++ aay_constructed = g_variant_builder_end (&builder);
++
++ /* Verify that the constructed array is in normal form, and its serialised
++ * form is `b'\0' * 128`. */
++ g_assert_true (g_variant_is_normal_form (aay_constructed));
++ g_assert_cmpuint (g_variant_n_children (aay_constructed), ==, 128);
++ g_assert_cmpuint (g_variant_get_size (aay_constructed), ==, 128);
++
++ data = g_variant_get_data (aay_constructed);
++ for (i = 0; i < g_variant_get_size (aay_constructed); i++)
++ g_assert_cmpuint (data[i], ==, 0);
++
++ /* Construct a serialised `aay` GVariant which is `b'\0' * 256`. This has to
++ * be a non-normal form of `[[] * 128]`, with 2-byte-long offset table
++ * entries, because each offset table entry has to be able to reference all of
++ * the byte boundaries in the container. All the entries in the offset table
++ * are zero, so all the elements of the array are zero-sized. */
++ data = data_owned = g_malloc0 (256);
++ aay_deserialised = g_variant_new_from_data (G_VARIANT_TYPE ("aay"),
++ data,
++ 256,
++ FALSE,
++ g_free,
++ g_steal_pointer (&data_owned));
++
++ g_assert_false (g_variant_is_normal_form (aay_deserialised));
++ g_assert_cmpuint (g_variant_n_children (aay_deserialised), ==, 128);
++ g_assert_cmpuint (g_variant_get_size (aay_deserialised), ==, 256);
++
++ data = g_variant_get_data (aay_deserialised);
++ for (i = 0; i < g_variant_get_size (aay_deserialised); i++)
++ g_assert_cmpuint (data[i], ==, 0);
++
++ /* Get its normal form. That should change the serialised size. */
++ aay_normalised = g_variant_get_normal_form (aay_deserialised);
++
++ g_assert_true (g_variant_is_normal_form (aay_normalised));
++ g_assert_cmpuint (g_variant_n_children (aay_normalised), ==, 128);
++ g_assert_cmpuint (g_variant_get_size (aay_normalised), ==, 128);
++
++ data = g_variant_get_data (aay_normalised);
++ for (i = 0; i < g_variant_get_size (aay_normalised); i++)
++ g_assert_cmpuint (data[i], ==, 0);
++
++ g_variant_unref (aay_normalised);
++ g_variant_unref (aay_deserialised);
++ g_variant_unref (aay_constructed);
++}
++
+ /* Test that a tuple with invalidly large values in its offset table is
+ * normalised successfully without looping infinitely. */
+ static void
+@@ -5416,6 +5496,98 @@ test_normal_checking_tuple_offsets4 (void)
+ g_variant_unref (variant);
+ }
+
++/* Test that an otherwise-valid serialised GVariant is considered non-normal if
++ * its offset table entries are too wide.
++ *
++ * See §2.3.6 (Framing Offsets) of the GVariant specification. */
++static void
++test_normal_checking_tuple_offsets_minimal_sized (void)
++{
++ GString *type_string = NULL;
++ GVariantBuilder builder;
++ gsize i;
++ GVariant *ray_constructed = NULL;
++ const guint8 *data = NULL;
++ guint8 *data_owned = NULL;
++ GVariant *ray_deserialised = NULL;
++ GVariant *ray_normalised = NULL;
++
++ /* Construct a tuple of type (ay…ay), consisting of 129 members which are each
++ * an empty array, i.e. `([] * 129)`. This is chosen because the inner
++ * members are variable sized, so the outer tuple must have an offset table,
++ * but they are also zero-sized when serialised. So the serialised
++ * representation of @ray_constructed consists entirely of its offset table,
++ * which is entirely zeroes.
++ *
++ * The tuple is chosen to be 129 members long because that means it has 128
++ * offset table entries which are 1 byte long each. If the members in the
++ * tuple were non-zero-sized (to the extent that the overall tuple is ≥256
++ * bytes long), the offset table entries would end up being 2 bytes long.
++ *
++ * 129 members are used unlike 128 array elements in
++ * test_normal_checking_array_offsets_minimal_sized(), because the last member
++ * in a tuple never needs an offset table entry. */
++ type_string = g_string_new ("");
++ g_string_append_c (type_string, '(');
++ for (i = 0; i < 129; i++)
++ g_string_append (type_string, "ay");
++ g_string_append_c (type_string, ')');
++
++ g_variant_builder_init (&builder, G_VARIANT_TYPE (type_string->str));
++
++ for (i = 0; i < 129; i++)
++ g_variant_builder_add_value (&builder, g_variant_new_array (G_VARIANT_TYPE_BYTE, NULL, 0));
++
++ ray_constructed = g_variant_builder_end (&builder);
++
++ /* Verify that the constructed tuple is in normal form, and its serialised
++ * form is `b'\0' * 128`. */
++ g_assert_true (g_variant_is_normal_form (ray_constructed));
++ g_assert_cmpuint (g_variant_n_children (ray_constructed), ==, 129);
++ g_assert_cmpuint (g_variant_get_size (ray_constructed), ==, 128);
++
++ data = g_variant_get_data (ray_constructed);
++ for (i = 0; i < g_variant_get_size (ray_constructed); i++)
++ g_assert_cmpuint (data[i], ==, 0);
++
++ /* Construct a serialised `(ay…ay)` GVariant which is `b'\0' * 256`. This has
++ * to be a non-normal form of `([] * 129)`, with 2-byte-long offset table
++ * entries, because each offset table entry has to be able to reference all of
++ * the byte boundaries in the container. All the entries in the offset table
++ * are zero, so all the members of the tuple are zero-sized. */
++ data = data_owned = g_malloc0 (256);
++ ray_deserialised = g_variant_new_from_data (G_VARIANT_TYPE (type_string->str),
++ data,
++ 256,
++ FALSE,
++ g_free,
++ g_steal_pointer (&data_owned));
++
++ g_assert_false (g_variant_is_normal_form (ray_deserialised));
++ g_assert_cmpuint (g_variant_n_children (ray_deserialised), ==, 129);
++ g_assert_cmpuint (g_variant_get_size (ray_deserialised), ==, 256);
++
++ data = g_variant_get_data (ray_deserialised);
++ for (i = 0; i < g_variant_get_size (ray_deserialised); i++)
++ g_assert_cmpuint (data[i], ==, 0);
++
++ /* Get its normal form. That should change the serialised size. */
++ ray_normalised = g_variant_get_normal_form (ray_deserialised);
++
++ g_assert_true (g_variant_is_normal_form (ray_normalised));
++ g_assert_cmpuint (g_variant_n_children (ray_normalised), ==, 129);
++ g_assert_cmpuint (g_variant_get_size (ray_normalised), ==, 128);
++
++ data = g_variant_get_data (ray_normalised);
++ for (i = 0; i < g_variant_get_size (ray_normalised); i++)
++ g_assert_cmpuint (data[i], ==, 0);
++
++ g_variant_unref (ray_normalised);
++ g_variant_unref (ray_deserialised);
++ g_variant_unref (ray_constructed);
++ g_string_free (type_string, TRUE);
++}
++
+ /* Test that an empty object path is normalised successfully to the base object
+ * path, ‘/’. */
+ static void
+@@ -5564,6 +5736,8 @@ main (int argc, char **argv)
+ test_normal_checking_array_offsets);
+ g_test_add_func ("/gvariant/normal-checking/array-offsets2",
+ test_normal_checking_array_offsets2);
++ g_test_add_func ("/gvariant/normal-checking/array-offsets/minimal-sized",
++ test_normal_checking_array_offsets_minimal_sized);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets",
+ test_normal_checking_tuple_offsets);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets2",
+@@ -5572,6 +5746,8 @@ main (int argc, char **argv)
+ test_normal_checking_tuple_offsets3);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets4",
+ test_normal_checking_tuple_offsets4);
++ g_test_add_func ("/gvariant/normal-checking/tuple-offsets/minimal-sized",
++ test_normal_checking_tuple_offsets_minimal_sized);
+ g_test_add_func ("/gvariant/normal-checking/empty-object-path",
+ test_normal_checking_empty_object_path);
+
+--
+GitLab
+
+
+From 781f05a22ef11d8a2177b4e9078978decec36dd0 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Thu, 27 Oct 2022 16:13:54 +0100
+Subject: [PATCH 17/18] gvariant: Fix g_variant_byteswap() returning non-normal
+ data sometimes
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+If `g_variant_byteswap()` was called on a non-normal variant of a type
+which doesn’t need byteswapping, it would return a non-normal output.
+
+That contradicts the documentation, which says that the return value is
+always in normal form.
+
+Fix the code so it matches the documentation.
+
+Includes a unit test.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Helps: #2797
+---
+ glib/gvariant.c | 8 +++++---
+ glib/tests/gvariant.c | 24 ++++++++++++++++++++++++
+ 2 files changed, 29 insertions(+), 3 deletions(-)
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index bb6be363e0..b3495d5cab 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -6083,14 +6083,16 @@ g_variant_byteswap (GVariant *value)
+ g_variant_serialised_byteswap (serialised);
+
+ bytes = g_bytes_new_take (serialised.data, serialised.size);
+- new = g_variant_new_from_bytes (g_variant_get_type (value), bytes, TRUE);
++ new = g_variant_ref_sink (g_variant_new_from_bytes (g_variant_get_type (value), bytes, TRUE));
+ g_bytes_unref (bytes);
+ }
+ else
+ /* contains no multi-byte data */
+- new = value;
++ new = g_variant_get_normal_form (value);
+
+- return g_variant_ref_sink (new);
++ g_assert (g_variant_is_trusted (new));
++
++ return g_steal_pointer (&new);
+ }
+
+ /**
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index 339edc4e04..bea16b5e58 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -3826,6 +3826,29 @@ test_gv_byteswap (void)
+ g_free (string);
+ }
+
++static void
++test_gv_byteswap_non_normal_non_aligned (void)
++{
++ const guint8 data[] = { 0x02 };
++ GVariant *v = NULL;
++ GVariant *v_byteswapped = NULL;
++
++ g_test_summary ("Test that calling g_variant_byteswap() on a variant which "
++ "is in non-normal form and doesn’t need byteswapping returns "
++ "the same variant in normal form.");
++
++ v = g_variant_new_from_data (G_VARIANT_TYPE_BOOLEAN, data, sizeof (data), FALSE, NULL, NULL);
++ g_assert_false (g_variant_is_normal_form (v));
++
++ v_byteswapped = g_variant_byteswap (v);
++ g_assert_true (g_variant_is_normal_form (v_byteswapped));
++
++ g_assert_cmpvariant (v, v_byteswapped);
++
++ g_variant_unref (v);
++ g_variant_unref (v_byteswapped);
++}
++
+ static void
+ test_parser (void)
+ {
+@@ -5699,6 +5722,7 @@ main (int argc, char **argv)
+ g_test_add_func ("/gvariant/builder-memory", test_builder_memory);
+ g_test_add_func ("/gvariant/hashing", test_hashing);
+ g_test_add_func ("/gvariant/byteswap", test_gv_byteswap);
++ g_test_add_func ("/gvariant/byteswap/non-normal-non-aligned", test_gv_byteswap_non_normal_non_aligned);
+ g_test_add_func ("/gvariant/parser", test_parses);
+ g_test_add_func ("/gvariant/parser/integer-bounds", test_parser_integer_bounds);
+ g_test_add_func ("/gvariant/parser/recursion", test_parser_recursion);
+--
+GitLab
+
+
+From 7d7efce1d9c379fdd7d2ff58caea88f8806fdd2e Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@endlessos.org>
+Date: Thu, 27 Oct 2022 22:53:13 +0100
+Subject: [PATCH 18/18] gvariant: Allow g_variant_byteswap() to operate on
+ tree-form variants
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This avoids needing to always serialise a variant before byteswapping it.
+With variants in non-normal forms, serialisation can result in a large
+increase in size of the variant, and a lot of allocations for leaf
+`GVariant`s. This can lead to a denial of service attack.
+
+Avoid that by changing byteswapping so that it happens on the tree form
+of the variant if the input is in non-normal form. If the input is in
+normal form (either serialised or in tree form), continue using the
+existing code as byteswapping an already-serialised normal variant is
+about 3× faster than byteswapping on the equivalent tree form.
+
+The existing unit tests cover byteswapping well, but need some
+adaptation so that they operate on tree form variants too.
+
+I considered dropping the serialised byteswapping code and doing all
+byteswapping on tree-form variants, as that would make maintenance
+simpler (avoiding having two parallel implementations of byteswapping).
+However, most inputs to `g_variant_byteswap()` are likely to be
+serialised variants (coming from a byte array of input from some foreign
+source) and most of them are going to be in normal form (as corruption
+and malicious action are rare). So getting rid of the serialised
+byteswapping code would impose quite a performance penalty on the common
+case.
+
+Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
+
+Fixes: #2797
+---
+ glib/gvariant.c | 87 ++++++++++++++++++++++++++++++++-----------
+ glib/tests/gvariant.c | 57 ++++++++++++++++++++++++----
+ 2 files changed, 115 insertions(+), 29 deletions(-)
+
+diff --git a/glib/gvariant.c b/glib/gvariant.c
+index b3495d5cab..95904bd382 100644
+--- a/glib/gvariant.c
++++ b/glib/gvariant.c
+@@ -5852,7 +5852,8 @@ g_variant_iter_loop (GVariantIter *iter,
+
+ /* Serialized data {{{1 */
+ static GVariant *
+-g_variant_deep_copy (GVariant *value)
++g_variant_deep_copy (GVariant *value,
++ gboolean byteswap)
+ {
+ switch (g_variant_classify (value))
+ {
+@@ -5869,7 +5870,7 @@ g_variant_deep_copy (GVariant *value)
+ for (i = 0, n_children = g_variant_n_children (value); i < n_children; i++)
+ {
+ GVariant *child = g_variant_get_child_value (value, i);
+- g_variant_builder_add_value (&builder, g_variant_deep_copy (child));
++ g_variant_builder_add_value (&builder, g_variant_deep_copy (child, byteswap));
+ g_variant_unref (child);
+ }
+
+@@ -5920,7 +5921,7 @@ g_variant_deep_copy (GVariant *value)
+ * be non-normal for reasons other than invalid offset table
+ * entries. As they are all the same type, they will all have
+ * the same default value though, so keep that around. */
+- g_variant_builder_add_value (&builder, g_variant_deep_copy (child));
++ g_variant_builder_add_value (&builder, g_variant_deep_copy (child, byteswap));
+ }
+ else if (child == NULL && first_invalid_child_deep_copy != NULL)
+ {
+@@ -5929,7 +5930,7 @@ g_variant_deep_copy (GVariant *value)
+ else if (child == NULL)
+ {
+ child = g_variant_get_child_value (value, i);
+- first_invalid_child_deep_copy = g_variant_ref_sink (g_variant_deep_copy (child));
++ first_invalid_child_deep_copy = g_variant_ref_sink (g_variant_deep_copy (child, byteswap));
+ g_variant_builder_add_value (&builder, first_invalid_child_deep_copy);
+ }
+
+@@ -5948,28 +5949,63 @@ g_variant_deep_copy (GVariant *value)
+ return g_variant_new_byte (g_variant_get_byte (value));
+
+ case G_VARIANT_CLASS_INT16:
+- return g_variant_new_int16 (g_variant_get_int16 (value));
++ if (byteswap)
++ return g_variant_new_int16 (GUINT16_SWAP_LE_BE (g_variant_get_int16 (value)));
++ else
++ return g_variant_new_int16 (g_variant_get_int16 (value));
+
+ case G_VARIANT_CLASS_UINT16:
+- return g_variant_new_uint16 (g_variant_get_uint16 (value));
++ if (byteswap)
++ return g_variant_new_uint16 (GUINT16_SWAP_LE_BE (g_variant_get_uint16 (value)));
++ else
++ return g_variant_new_uint16 (g_variant_get_uint16 (value));
+
+ case G_VARIANT_CLASS_INT32:
+- return g_variant_new_int32 (g_variant_get_int32 (value));
++ if (byteswap)
++ return g_variant_new_int32 (GUINT32_SWAP_LE_BE (g_variant_get_int32 (value)));
++ else
++ return g_variant_new_int32 (g_variant_get_int32 (value));
+
+ case G_VARIANT_CLASS_UINT32:
+- return g_variant_new_uint32 (g_variant_get_uint32 (value));
++ if (byteswap)
++ return g_variant_new_uint32 (GUINT32_SWAP_LE_BE (g_variant_get_uint32 (value)));
++ else
++ return g_variant_new_uint32 (g_variant_get_uint32 (value));
+
+ case G_VARIANT_CLASS_INT64:
+- return g_variant_new_int64 (g_variant_get_int64 (value));
++ if (byteswap)
++ return g_variant_new_int64 (GUINT64_SWAP_LE_BE (g_variant_get_int64 (value)));
++ else
++ return g_variant_new_int64 (g_variant_get_int64 (value));
+
+ case G_VARIANT_CLASS_UINT64:
+- return g_variant_new_uint64 (g_variant_get_uint64 (value));
++ if (byteswap)
++ return g_variant_new_uint64 (GUINT64_SWAP_LE_BE (g_variant_get_uint64 (value)));
++ else
++ return g_variant_new_uint64 (g_variant_get_uint64 (value));
+
+ case G_VARIANT_CLASS_HANDLE:
+- return g_variant_new_handle (g_variant_get_handle (value));
++ if (byteswap)
++ return g_variant_new_handle (GUINT32_SWAP_LE_BE (g_variant_get_handle (value)));
++ else
++ return g_variant_new_handle (g_variant_get_handle (value));
+
+ case G_VARIANT_CLASS_DOUBLE:
+- return g_variant_new_double (g_variant_get_double (value));
++ if (byteswap)
++ {
++ /* We have to convert the double to a uint64 here using a union,
++ * because a cast will round it numerically. */
++ union
++ {
++ guint64 u64;
++ gdouble dbl;
++ } u1, u2;
++ u1.dbl = g_variant_get_double (value);
++ u2.u64 = GUINT64_SWAP_LE_BE (u1.u64);
++ return g_variant_new_double (u2.dbl);
++ }
++ else
++ return g_variant_new_double (g_variant_get_double (value));
+
+ case G_VARIANT_CLASS_STRING:
+ return g_variant_new_string (g_variant_get_string (value, NULL));
+@@ -6026,7 +6062,7 @@ g_variant_get_normal_form (GVariant *value)
+ if (g_variant_is_normal_form (value))
+ return g_variant_ref (value);
+
+- trusted = g_variant_deep_copy (value);
++ trusted = g_variant_deep_copy (value, FALSE);
+ g_assert (g_variant_is_trusted (trusted));
+
+ return g_variant_ref_sink (trusted);
+@@ -6046,6 +6082,11 @@ g_variant_get_normal_form (GVariant *value)
+ * contain multi-byte numeric data. That include strings, booleans,
+ * bytes and containers containing only these things (recursively).
+ *
++ * While this function can safely handle untrusted, non-normal data, it is
++ * recommended to check whether the input is in normal form beforehand, using
++ * g_variant_is_normal_form(), and to reject non-normal inputs if your
++ * application can be strict about what inputs it rejects.
++ *
+ * The returned value is always in normal form and is marked as trusted.
+ *
+ * Returns: (transfer full): the byteswapped form of @value
+@@ -6063,22 +6104,21 @@ g_variant_byteswap (GVariant *value)
+
+ g_variant_type_info_query (type_info, &alignment, NULL);
+
+- if (alignment)
+- /* (potentially) contains multi-byte numeric data */
++ if (alignment && g_variant_is_normal_form (value))
+ {
++ /* (potentially) contains multi-byte numeric data, but is also already in
++ * normal form so we can use a faster byteswapping codepath on the
++ * serialised data */
+ GVariantSerialised serialised = { 0, };
+- GVariant *trusted;
+ GBytes *bytes;
+
+- trusted = g_variant_get_normal_form (value);
+- serialised.type_info = g_variant_get_type_info (trusted);
+- serialised.size = g_variant_get_size (trusted);
++ serialised.type_info = g_variant_get_type_info (value);
++ serialised.size = g_variant_get_size (value);
+ serialised.data = g_malloc (serialised.size);
+- serialised.depth = g_variant_get_depth (trusted);
++ serialised.depth = g_variant_get_depth (value);
+ serialised.ordered_offsets_up_to = G_MAXSIZE; /* operating on the normal form */
+ serialised.checked_offsets_up_to = G_MAXSIZE;
+- g_variant_store (trusted, serialised.data);
+- g_variant_unref (trusted);
++ g_variant_store (value, serialised.data);
+
+ g_variant_serialised_byteswap (serialised);
+
+@@ -6086,6 +6126,9 @@ g_variant_byteswap (GVariant *value)
+ new = g_variant_ref_sink (g_variant_new_from_bytes (g_variant_get_type (value), bytes, TRUE));
+ g_bytes_unref (bytes);
+ }
++ else if (alignment)
++ /* (potentially) contains multi-byte numeric data */
++ new = g_variant_ref_sink (g_variant_deep_copy (value, TRUE));
+ else
+ /* contains no multi-byte data */
+ new = g_variant_get_normal_form (value);
+diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
+index bea16b5e58..ba837af48d 100644
+--- a/glib/tests/gvariant.c
++++ b/glib/tests/gvariant.c
+@@ -2288,24 +2288,67 @@ serialise_tree (TreeInstance *tree,
+ static void
+ test_byteswap (void)
+ {
+- GVariantSerialised one = { 0, }, two = { 0, };
++ GVariantSerialised one = { 0, }, two = { 0, }, three = { 0, };
+ TreeInstance *tree;
+-
++ GVariant *one_variant = NULL;
++ GVariant *two_variant = NULL;
++ GVariant *two_byteswapped = NULL;
++ GVariant *three_variant = NULL;
++ GVariant *three_byteswapped = NULL;
++ guint8 *three_data_copy = NULL;
++ gsize three_size_copy = 0;
++
++ /* Write a tree out twice, once normally and once byteswapped. */
+ tree = tree_instance_new (NULL, 3);
+ serialise_tree (tree, &one);
+
++ one_variant = g_variant_new_from_data (G_VARIANT_TYPE (g_variant_type_info_get_type_string (one.type_info)),
++ one.data, one.size, FALSE, NULL, NULL);
++
+ i_am_writing_byteswapped = TRUE;
+ serialise_tree (tree, &two);
++ serialise_tree (tree, &three);
+ i_am_writing_byteswapped = FALSE;
+
+- g_variant_serialised_byteswap (two);
+-
+- g_assert_cmpmem (one.data, one.size, two.data, two.size);
+- g_assert_cmpuint (one.depth, ==, two.depth);
+-
++ /* Swap the first byteswapped one back using the function we want to test. */
++ two_variant = g_variant_new_from_data (G_VARIANT_TYPE (g_variant_type_info_get_type_string (two.type_info)),
++ two.data, two.size, FALSE, NULL, NULL);
++ two_byteswapped = g_variant_byteswap (two_variant);
++
++ /* Make the second byteswapped one non-normal (hopefully), and then byteswap
++ * it back using the function we want to test in its non-normal mode.
++ * This might not work because it’s not necessarily possible to make an
++ * arbitrary random variant non-normal. Adding a single zero byte to the end
++ * often makes something non-normal but still readable. */
++ three_size_copy = three.size + 1;
++ three_data_copy = g_malloc (three_size_copy);
++ memcpy (three_data_copy, three.data, three.size);
++ three_data_copy[three.size] = '\0';
++
++ three_variant = g_variant_new_from_data (G_VARIANT_TYPE (g_variant_type_info_get_type_string (three.type_info)),
++ three_data_copy, three_size_copy, FALSE, NULL, NULL);
++ three_byteswapped = g_variant_byteswap (three_variant);
++
++ /* Check they’re the same. We can always compare @one_variant and
++ * @two_byteswapped. We can only compare @two_byteswapped and
++ * @three_byteswapped if @two_variant and @three_variant are equal: in that
++ * case, the corruption to @three_variant was enough to make it non-normal but
++ * not enough to change its value. */
++ g_assert_cmpvariant (one_variant, two_byteswapped);
++
++ if (g_variant_equal (two_variant, three_variant))
++ g_assert_cmpvariant (two_byteswapped, three_byteswapped);
++
++ g_variant_unref (three_byteswapped);
++ g_variant_unref (three_variant);
++ g_variant_unref (two_byteswapped);
++ g_variant_unref (two_variant);
++ g_variant_unref (one_variant);
+ tree_instance_free (tree);
+ g_free (one.data);
+ g_free (two.data);
++ g_free (three.data);
++ g_free (three_data_copy);
+ }
+
+ static void
+--
+GitLab
+
+
diff --git a/dev-util/glib-utils/glib-utils-2.68.3-r1.ebuild b/dev-util/glib-utils/glib-utils-2.68.3-r2.ebuild
similarity index 100%
rename from dev-util/glib-utils/glib-utils-2.68.3-r1.ebuild
rename to dev-util/glib-utils/glib-utils-2.68.3-r2.ebuild
diff --git a/dev-util/glib-utils/glib-utils-2.68.3.ebuild b/dev-util/glib-utils/glib-utils-2.68.3.ebuild
index 03b0ab5..1bba684 100644
--- a/dev-util/glib-utils/glib-utils-2.68.3.ebuild
+++ b/dev-util/glib-utils/glib-utils-2.68.3.ebuild
@@ -26,6 +26,11 @@
cros-host? ( ${BDEPEND} )
"
+PATCHES=(
+ "${FILESDIR}"/glib-2.74.1-normal-form-handling-gvariant.patch
+ "${FILESDIR}"/glib-2.74.1-normal-form-handling-gvariant-patch-fixes.patch
+)
+
src_configure() { :; }
do_xsltproc_command() {