static-delta: Validate decompressed size against declared usize
authorJoseph Marrero Corchado <jmarrero@redhat.com>
Wed, 29 Jul 2026 00:19:42 +0000 (20:19 -0400)
committerJoseph Marrero Corchado <jmarrero@redhat.com>
Wed, 29 Jul 2026 00:19:42 +0000 (20:19 -0400)
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 <walters@verbum.org>
src/libostree/ostree-repo-pull.c
src/libostree/ostree-repo-static-delta-core.c
src/libostree/ostree-repo-static-delta-private.h

index 9989415fa22cdcd39b2595ded79eaff0eb1cfc3f..585850dece0dd8585a6195fd5bfa38a085b23307 100644 (file)
@@ -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;
index 4f51a4f5a6396c3f2fb3fd4091932c30fb9c724e..cd974705bc887abb2027e02331cf2829f4ea898d 100644 (file)
@@ -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;
 
   {
index d711de62b91b655230f50c253eea711313a8beeb..137ec60aa267fbd0fd4b97d99e1757c207c8cf63 100644 (file)
@@ -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
 {