Skip to content

Commit 1858d3d

Browse files
committed
core: Always sort incoming xattrs
When recomputing selinux attrs during commit, we weren't sorting, which could cause various issues like fsck failures. This is a big hammer; change things so we always canonicalize (i.e. sort) the incoming xattrs when creating a file header and directory metadata. I think almost all places in the code were already keeping things sorted, but it's better to ensure correctness first. If we ever have some performance issue (I'm doubtful) we could add something like `_ostree_file_header_known_canonicalized` or so. Closes: #3343 Signed-off-by: Colin Walters <walters@verbum.org>
1 parent 295841b commit 1858d3d

File tree

2 files changed

+132
-16
lines changed

2 files changed

+132
-16
lines changed

src/libostree/ostree-core.c

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ G_STATIC_ASSERT (OSTREE_REPO_MODE_BARE_USER_ONLY == 3);
4343
G_STATIC_ASSERT (OSTREE_REPO_MODE_BARE_SPLIT_XATTRS == 4);
4444

4545
static GBytes *variant_to_lenprefixed_buffer (GVariant *variant);
46+
static GVariant *canonicalize_xattrs (GVariant *xattrs);
4647

4748
#define ALIGN_VALUE(this, boundary) \
4849
((((unsigned long)(this)) + (((unsigned long)(boundary)) - 1)) \
@@ -302,6 +303,47 @@ ostree_validate_collection_id (const char *collection_id, GError **error)
302303
return TRUE;
303304
}
304305

306+
static int
307+
compare_xattrs (const void *a_pp, const void *b_pp)
308+
{
309+
GVariant *a = *(GVariant **)a_pp;
310+
GVariant *b = *(GVariant **)b_pp;
311+
const char *name_a;
312+
const char *name_b;
313+
g_variant_get (a, "(^&ay@ay)", &name_a, NULL);
314+
g_variant_get (b, "(^&ay@ay)", &name_b, NULL);
315+
316+
return strcmp (name_a, name_b);
317+
}
318+
319+
// Sort xattrs by name
320+
static GVariant *
321+
canonicalize_xattrs (GVariant *xattrs)
322+
{
323+
// We always need to provide data, so NULL is canonicalized to the empty array
324+
if (xattrs == NULL)
325+
return g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0));
326+
327+
g_autoptr (GPtrArray) xattr_array
328+
= g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);
329+
330+
const guint n = g_variant_n_children (xattrs);
331+
for (guint i = 0; i < n; i++)
332+
{
333+
GVariant *child = g_variant_get_child_value (xattrs, i);
334+
g_ptr_array_add (xattr_array, child);
335+
}
336+
337+
g_ptr_array_sort (xattr_array, compare_xattrs);
338+
339+
GVariantBuilder builder;
340+
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
341+
for (guint i = 0; i < xattr_array->len; i++)
342+
g_variant_builder_add_value (&builder, xattr_array->pdata[i]);
343+
344+
return g_variant_ref_sink (g_variant_builder_end (&builder));
345+
}
346+
305347
/* The file header is part of the "object stream" format
306348
* that's not compressed. It's comprised of uid,gid,mode,
307349
* and possibly symlink targets from @file_info, as well
@@ -321,13 +363,12 @@ _ostree_file_header_new (GFileInfo *file_info, GVariant *xattrs)
321363
else
322364
symlink_target = "";
323365

324-
g_autoptr (GVariant) tmp_xattrs = NULL;
325-
if (xattrs == NULL)
326-
tmp_xattrs = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0));
366+
// We always sort the xattrs now to ensure everything is in normal/canonical form.
367+
g_autoptr (GVariant) tmp_xattrs = canonicalize_xattrs (xattrs);
327368

328369
g_autoptr (GVariant) ret
329370
= g_variant_new ("(uuuus@a(ayay))", GUINT32_TO_BE (uid), GUINT32_TO_BE (gid),
330-
GUINT32_TO_BE (mode), 0, symlink_target, xattrs ?: tmp_xattrs);
371+
GUINT32_TO_BE (mode), 0, symlink_target, tmp_xattrs);
331372
return variant_to_lenprefixed_buffer (g_variant_ref_sink (ret));
332373
}
333374

@@ -1111,11 +1152,13 @@ ostree_create_directory_metadata (GFileInfo *dir_info, GVariant *xattrs)
11111152
{
11121153
GVariant *ret_metadata = NULL;
11131154

1155+
// We always sort the xattrs now to ensure everything is in normal/canonical form.
1156+
g_autoptr (GVariant) tmp_xattrs = canonicalize_xattrs (xattrs);
1157+
11141158
ret_metadata = g_variant_new (
11151159
"(uuu@a(ayay))", GUINT32_TO_BE (g_file_info_get_attribute_uint32 (dir_info, "unix::uid")),
11161160
GUINT32_TO_BE (g_file_info_get_attribute_uint32 (dir_info, "unix::gid")),
1117-
GUINT32_TO_BE (g_file_info_get_attribute_uint32 (dir_info, "unix::mode")),
1118-
xattrs ? xattrs : g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0));
1161+
GUINT32_TO_BE (g_file_info_get_attribute_uint32 (dir_info, "unix::mode")), tmp_xattrs);
11191162
g_variant_ref_sink (ret_metadata);
11201163

11211164
return ret_metadata;
@@ -2278,21 +2321,30 @@ ostree_validate_structureof_file_mode (guint32 mode, GError **error)
22782321
}
22792322

22802323
/* Currently ostree imposes no restrictions on xattrs on its own;
2281-
* they can e.g. be arbitrariliy sized or in number.
2282-
* However, we do validate the key is non-empty, as that is known
2283-
* to always fail.
2324+
* they can e.g. be arbitrariliy sized or in number. The xattrs
2325+
* must be sorted by name (without duplicates), and keys cannot be empty.
22842326
*/
22852327
gboolean
22862328
_ostree_validate_structureof_xattrs (GVariant *xattrs, GError **error)
22872329
{
22882330
const guint n = g_variant_n_children (xattrs);
2331+
const char *previous = NULL;
22892332
for (guint i = 0; i < n; i++)
22902333
{
2291-
const guint8 *name;
2334+
const char *name;
22922335
g_autoptr (GVariant) value = NULL;
22932336
g_variant_get_child (xattrs, i, "(^&ay@ay)", &name, &value);
22942337
if (!*name)
22952338
return glnx_throw (error, "Invalid xattr name (empty or missing NUL) index=%d", i);
2339+
if (previous)
2340+
{
2341+
int cmp = strcmp (previous, name);
2342+
if (cmp == 0)
2343+
return glnx_throw (error, "Duplicate xattr name, index=%d", i);
2344+
else if (cmp > 0)
2345+
return glnx_throw (error, "Incorrectly sorted xattr name, index=%d", i);
2346+
}
2347+
previous = name;
22962348
i++;
22972349
}
22982350
return TRUE;

tests/test-basic-c.c

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,12 @@ test_raw_file_to_archive_stream (gconstpointer data)
130130
}
131131

132132
static gboolean
133-
hi_content_stream_new (GInputStream **out_stream, guint64 *out_length, GError **error)
133+
basic_regfile_content_stream_new (const char *contents, GVariant *xattr, GInputStream **out_stream,
134+
guint64 *out_length, GError **error)
134135
{
135-
static const char hi[] = "hi";
136-
const size_t len = sizeof (hi) - 1;
136+
const size_t len = strlen (contents);
137137
g_autoptr (GMemoryInputStream) hi_memstream
138-
= (GMemoryInputStream *)g_memory_input_stream_new_from_data (hi, len, NULL);
138+
= (GMemoryInputStream *)g_memory_input_stream_new_from_data (contents, len, NULL);
139139
g_autoptr (GFileInfo) finfo = g_file_info_new ();
140140
g_file_info_set_attribute_uint32 (finfo, "standard::type", G_FILE_TYPE_REGULAR);
141141
g_file_info_set_attribute_boolean (finfo, "standard::is-symlink", FALSE);
@@ -147,6 +147,12 @@ hi_content_stream_new (GInputStream **out_stream, guint64 *out_length, GError **
147147
out_length, NULL, error);
148148
}
149149

150+
static gboolean
151+
hi_content_stream_new (GInputStream **out_stream, guint64 *out_length, GError **error)
152+
{
153+
return basic_regfile_content_stream_new ("hi", NULL, out_stream, out_length, error);
154+
}
155+
150156
static void
151157
test_validate_remotename (void)
152158
{
@@ -174,6 +180,8 @@ test_object_writes (gconstpointer data)
174180

175181
static const char hi_sha256[]
176182
= "2301b5923720c3edc1f0467addb5c287fd5559e3e0cd1396e7f1edb6b01be9f0";
183+
static const char invalid_hi_sha256[]
184+
= "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe";
177185

178186
/* Successful content write */
179187
{
@@ -193,12 +201,68 @@ test_object_writes (gconstpointer data)
193201
hi_content_stream_new (&hi_memstream, &len, &error);
194202
g_assert_no_error (error);
195203
g_autofree guchar *csum = NULL;
196-
static const char invalid_hi_sha256[]
197-
= "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe";
198204
g_assert (!ostree_repo_write_content (repo, invalid_hi_sha256, hi_memstream, len, &csum, NULL,
199205
&error));
200206
g_assert (error);
201207
g_assert (strstr (error->message, "Corrupted file object"));
208+
g_clear_error (&error);
209+
}
210+
211+
/* Test a basic regfile inline write, no xattrs */
212+
g_assert (ostree_repo_write_regfile_inline (repo, hi_sha256, 0, 0, S_IFREG | 0644, NULL,
213+
(guint8 *)"hi", 2, NULL, &error));
214+
g_assert_no_error (error);
215+
216+
/* Test a basic regfile inline write, erroring on checksum */
217+
g_assert (!ostree_repo_write_regfile_inline (repo, invalid_hi_sha256, 0, 0, S_IFREG | 0644, NULL,
218+
(guint8 *)"hi", 2, NULL, &error));
219+
g_assert (error != NULL);
220+
g_clear_error (&error);
221+
222+
static const char expected_sha256_with_xattrs[]
223+
= "72473a9e8ace75f89f1504137cfb134feb5372bc1d97e04eb5300e24ad836153";
224+
225+
GVariantBuilder builder;
226+
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
227+
g_variant_builder_add (&builder, "(^ay^ay)", "security.selinux", "foo_t");
228+
g_variant_builder_add (&builder, "(^ay^ay)", "user.blah", "somedata");
229+
g_autoptr (GVariant) inorder_xattrs = g_variant_ref_sink (g_variant_builder_end (&builder));
230+
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
231+
g_variant_builder_add (&builder, "(^ay^ay)", "user.blah", "somedata");
232+
g_variant_builder_add (&builder, "(^ay^ay)", "security.selinux", "foo_t");
233+
g_autoptr (GVariant) unsorted_xattrs = g_variant_ref_sink (g_variant_builder_end (&builder));
234+
235+
/* Now test with xattrs */
236+
g_assert (ostree_repo_write_regfile_inline (repo, expected_sha256_with_xattrs, 0, 0,
237+
S_IFREG | 0644, inorder_xattrs, (guint8 *)"hi", 2,
238+
NULL, &error));
239+
g_assert_no_error (error);
240+
241+
/* And now with a swapped order */
242+
g_assert (ostree_repo_write_regfile_inline (repo, expected_sha256_with_xattrs, 0, 0,
243+
S_IFREG | 0644, unsorted_xattrs, (guint8 *)"hi", 2,
244+
NULL, &error));
245+
g_assert_no_error (error);
246+
247+
/* Tests of directory metadata */
248+
static const char expected_dirmeta_sha256[]
249+
= "f773ab98198d8e46f77be6ffff5fc1920888c0af5794426c3b1461131d509f34";
250+
g_autoptr (GFileInfo) fi = g_file_info_new ();
251+
g_file_info_set_attribute_uint32 (fi, "unix::uid", 0);
252+
g_file_info_set_attribute_uint32 (fi, "unix::gid", 0);
253+
g_file_info_set_attribute_uint32 (fi, "unix::mode", (0755 | S_IFDIR));
254+
{
255+
g_autoptr (GVariant) dirmeta = ostree_create_directory_metadata (fi, inorder_xattrs);
256+
ostree_repo_write_metadata (repo, OSTREE_OBJECT_TYPE_DIR_META, expected_dirmeta_sha256, dirmeta,
257+
NULL, NULL, &error);
258+
g_assert_no_error (error);
259+
}
260+
/* And now with unsorted xattrs */
261+
{
262+
g_autoptr (GVariant) dirmeta = ostree_create_directory_metadata (fi, unsorted_xattrs);
263+
ostree_repo_write_metadata (repo, OSTREE_OBJECT_TYPE_DIR_META, expected_dirmeta_sha256, dirmeta,
264+
NULL, NULL, &error);
265+
g_assert_no_error (error);
202266
}
203267
}
204268

0 commit comments

Comments
 (0)