From: Joseph Marrero Corchado Date: Wed, 29 Jul 2026 00:19:42 +0000 (-0400) Subject: static-delta: Validate decompressed size against declared usize X-Git-Tag: archive/raspbian/2026.4-1+rpi1^2~9^2~1^2~14^2 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=a0ede4d33e0a0eb56052ef0a6ac5d8277da0057c;p=ostree.git static-delta: Validate decompressed size against declared usize Each delta part header already declares the expected uncompressed size of the resulting objects. Pass that through to _ostree_static_delta_part_open() and use it as the decompression limit when it is smaller than the hard cap. This catches a crafted delta that declares a small usize (passing the free-space pre-check) but actually decompresses to something much larger. The hard cap (512 MiB) remains as a backstop when the declared size is unavailable (e.g. the show/dump path passes 0). Suggested-by: Colin Walters --- diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index 9989415f..585850de 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -91,6 +91,7 @@ typedef struct char *to_revision; guint i; guint64 size; + guint64 usize; guint n_retries_remaining; } FetchStaticDeltaData; @@ -1270,8 +1271,8 @@ static_deltapart_fetch_on_complete (GObject *object, GAsyncResult *result, gpoin in = g_unix_input_stream_new (g_steal_fd (&tmpf.fd), TRUE); /* TODO - make async */ - if (!_ostree_static_delta_part_open (in, NULL, 0, fetch_data->expected_checksum, &part, - pull_data->cancellable, error)) + if (!_ostree_static_delta_part_open (in, NULL, 0, fetch_data->expected_checksum, + fetch_data->usize, &part, pull_data->cancellable, error)) goto out; _ostree_static_delta_part_execute_async (pull_data->repo, fetch_data->objects, part, @@ -2201,6 +2202,7 @@ process_one_static_delta (OtPullData *pull_data, const char *from_revision, cons fetch_data->objects = g_variant_ref (objects); fetch_data->expected_checksum = ostree_checksum_from_bytes_v (csum_v); fetch_data->size = size; + fetch_data->usize = usize; fetch_data->i = i; fetch_data->n_retries_remaining = pull_data->n_network_retries; @@ -2212,7 +2214,7 @@ process_one_static_delta (OtPullData *pull_data, const char *from_revision, cons /* For inline parts we are relying on per-commit GPG, so don't bother checksumming. */ if (!_ostree_static_delta_part_open (memin, inline_part_bytes, OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM, NULL, - &inline_delta_part, cancellable, error)) + usize, &inline_delta_part, cancellable, error)) { fetch_static_delta_data_free (fetch_data); return FALSE; diff --git a/src/libostree/ostree-repo-static-delta-core.c b/src/libostree/ostree-repo-static-delta-core.c index 4f51a4f5..cd974705 100644 --- a/src/libostree/ostree-repo-static-delta-core.c +++ b/src/libostree/ostree-repo-static-delta-core.c @@ -586,7 +586,7 @@ ostree_repo_static_delta_execute_offline_with_signature (OstreeRepo *self, GFile delta_open_flags |= OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM; if (!_ostree_static_delta_part_open (part_in, inline_part_bytes, delta_open_flags, NULL, - &part, cancellable, error)) + usize, &part, cancellable, error)) return FALSE; } else @@ -598,8 +598,8 @@ ostree_repo_static_delta_execute_offline_with_signature (OstreeRepo *self, GFile part_in = g_unix_input_stream_new (part_fd, FALSE); - if (!_ostree_static_delta_part_open (part_in, NULL, delta_open_flags, checksum, &part, - cancellable, error)) + if (!_ostree_static_delta_part_open (part_in, NULL, delta_open_flags, checksum, usize, + &part, cancellable, error)) return FALSE; } @@ -636,11 +636,20 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, GFile *dir_or_file, gboolean _ostree_static_delta_part_open (GInputStream *part_in, GBytes *inline_part_bytes, OstreeStaticDeltaOpenFlags flags, const char *expected_checksum, - GVariant **out_part, GCancellable *cancellable, GError **error) + guint64 expected_usize, GVariant **out_part, + GCancellable *cancellable, GError **error) { const gboolean trusted = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_VARIANT_TRUSTED) > 0; const gboolean skip_checksum = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM) > 0; + /* Use the declared usize from the delta header as the decompression limit + * when available, capped to the hard maximum. If the caller passes 0 + * (e.g. the show/dump path) fall back to the hard cap alone. + */ + guint64 max_part_usize = OSTREE_STATIC_DELTA_PART_MAX_USIZE_BYTES; + if (expected_usize > 0 && expected_usize < max_part_usize) + max_part_usize = expected_usize; + /* We either take a fd or a GBytes reference */ g_return_val_if_fail (G_IS_FILE_DESCRIPTOR_BASED (part_in) || inline_part_bytes != NULL, FALSE); g_return_val_if_fail (skip_checksum || expected_checksum != NULL, FALSE); @@ -692,15 +701,14 @@ _ostree_static_delta_part_open (GInputStream *part_in, GBytes *inline_part_bytes g_variant_ref_sink (ret_part); } - /* Enforce the same size limit as for compressed parts so that an - * attacker cannot bypass the decompression-bomb defence simply by - * setting compression type to 0 (CVE / RHEL-189208). + /* Enforce the size limit so that an attacker cannot bypass the + * decompression-bomb defence by setting compression type to 0. */ - if (g_variant_get_size (ret_part) > OSTREE_STATIC_DELTA_PART_MAX_USIZE_BYTES) - return glnx_throw ( - error, - "Uncompressed delta part size %" G_GSIZE_FORMAT " exceeds maximum %" G_GUINT64_FORMAT, - g_variant_get_size (ret_part), (guint64)OSTREE_STATIC_DELTA_PART_MAX_USIZE_BYTES); + if (g_variant_get_size (ret_part) > max_part_usize) + return glnx_throw (error, + "Uncompressed delta part size %" G_GSIZE_FORMAT + " exceeds maximum %" G_GUINT64_FORMAT, + g_variant_get_size (ret_part), max_part_usize); if (!skip_checksum) g_checksum_update (checksum, g_variant_get_data (ret_part), g_variant_get_size (ret_part)); @@ -711,7 +719,7 @@ _ostree_static_delta_part_open (GInputStream *part_in, GBytes *inline_part_bytes g_autoptr (GConverter) decomp = (GConverter *)_ostree_lzma_decompressor_new (); g_autoptr (GInputStream) convin = g_converter_input_stream_new (source_in, decomp); g_autoptr (GBytes) buf = ot_map_anonymous_tmpfile_from_content_with_limit ( - convin, OSTREE_STATIC_DELTA_PART_MAX_USIZE_BYTES, cancellable, error); + convin, max_part_usize, cancellable, error); if (!buf) return FALSE; @@ -766,7 +774,7 @@ show_one_part (OstreeRepo *self, gboolean swap_endian, const char *from, const c g_autoptr (GVariant) part = NULL; if (!_ostree_static_delta_part_open (part_in, NULL, OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM, - NULL, &part, cancellable, error)) + NULL, 0, &part, cancellable, error)) return FALSE; { diff --git a/src/libostree/ostree-repo-static-delta-private.h b/src/libostree/ostree-repo-static-delta-private.h index d711de62..137ec60a 100644 --- a/src/libostree/ostree-repo-static-delta-private.h +++ b/src/libostree/ostree-repo-static-delta-private.h @@ -152,8 +152,9 @@ typedef enum gboolean _ostree_static_delta_part_open (GInputStream *part_in, GBytes *inline_part_bytes, OstreeStaticDeltaOpenFlags flags, - const char *expected_checksum, GVariant **out_part, - GCancellable *cancellable, GError **error); + const char *expected_checksum, guint64 expected_usize, + GVariant **out_part, GCancellable *cancellable, + GError **error); typedef struct {