Skip to content

panic!("encountered unsupported command code: 0") when enumerating repository objects #2044

Open
@bradlarsen

Description

@bradlarsen

What I'm seeing

Using Nosey Parker to scan a particular obscure repository from GitHub, I'm seeing a panic in the data::delta::apply function in gix-pack-0.59.1.

I'm seeing a crash when I run noseyparker scan -d test.np -j1 -vvv deqp.git, with a local clone of the https://github.com/elongbug/deqp repository. Here's a debug stack trace from that:

The application panicked (crashed).
Message:  encountered unsupported command code: 0
Location: /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gix-pack-0.59.1/src/data/delta.rs:60
[..,]
Full Log
The application panicked (crashed).
Message:  encountered unsupported command code: 0
Location: /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gix-pack-0.59.1/src/data/delta.rs:60

Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
⠒ Scanning content 1.55 GiB [00:01:14]                                                                                                                                                                                                                                                                                                                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                              ⋮ 9 frames hidden ⋮
10: gix_pack::data::delta::apply::h990db6d667116761
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gix-pack-0.59.1/src/data/delta.rs:60
      58 │                     .expect("delta copy from base: byte slices must match");
      59 │             }
      60 >             0 => panic!("encountered unsupported command code: 0"),
      61 │             size => {
      62 │                 std::io::Write::write(&mut target, &data[i..i + *size as usize])
11: gix_pack::data::file::decode::entry::<impl gix_pack::data::File>::resolve_deltas::h14b6fc9b4dbdff00
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gix-pack-0.59.1/src/data/file/decode/entry.rs:377
     375 │                 last_result_size = Some(result_size);
     376 │             }
     377 >             delta::apply(&source_buf[..base_size], &mut target_buf[..result_size], data);
     378 │             // use the target as source for the next delta
     379 │             std::mem::swap(&mut source_buf, &mut target_buf);
12: gix_pack::data::file::decode::entry::<impl gix_pack::data::File>::decode_entry::h485eb73abedf8206
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gix-pack-0.59.1/src/data/file/decode/entry.rs:186
     184 │                     })
     185 │             }
     186 >             OfsDelta { .. } | RefDelta { .. } => self.resolve_deltas(entry, resolve, inflate, out, delta_cache),
     187 │         }
     188 │     }
13: gix_odb::store_impls::dynamic::find::<impl gix_odb::store_impls::dynamic::Handle<S>>::try_find_cached_inner::h68d1ee577f15b8ae
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gix-odb-0.69.1/src/store_impls/dynamic/find.rs:151
     149 │                         let entry = pack.entry(pack_offset)?;
     150 │                         let header_size = entry.header_size();
     151 >                         let res = pack.decode_entry(
     152 │                             entry,
     153 │                             buffer,
14: gix_odb::store_impls::dynamic::find::<impl gix_pack::find_traits::Find for gix_odb::store_impls::dynamic::Handle<S>>::try_find_cached::h356f98a75f4393f2
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gix-odb-0.69.1/src/store_impls/dynamic/find.rs:356
     354 │         let mut snapshot = self.snapshot.borrow_mut();
     355 │         let mut inflate = self.inflate.borrow_mut();
     356 >         self.try_find_cached_inner(id, buffer, &mut inflate, pack_cache, &mut snapshot, None)
     357 │             .map_err(|err| Box::new(err) as _)
     358 │     }
15: gix_odb::cache::impls::<impl gix_pack::find_traits::Find for gix_odb::Cache<S>>::try_find_cached::h22b8bd39957c46de
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gix-odb-0.69.1/src/cache.rs:224
     222 │                 }
     223 │             }
     224 >             let possibly_obj = self.inner.try_find_cached(id.as_ref(), buffer, pack_cache)?;
     225 │             if let (Some(mut obj_cache), Some((obj, _location))) =
     226 │                 (self.object_cache.as_ref().map(RefCell::borrow_mut), &possibly_obj)
16: gix_odb::cache::impls::<impl gix_pack::find_traits::Find for gix_odb::Cache<S>>::try_find::hc3f634d96c8f0040
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gix-odb-0.69.1/src/cache.rs:208
     206 │         ) -> Result<Option<(Data<'a>, Option<Location>)>, gix_object::find::Error> {
     207 │             match self.pack_cache.as_ref().map(RefCell::borrow_mut) {
     208 >                 Some(mut pack_cache) => self.try_find_cached(id, buffer, pack_cache.deref_mut()),
     209 │                 None => self.try_find_cached(id, buffer, &mut gix_pack::cache::Never),
     210 │             }
17: gix_odb::cache::impls::<impl gix_object::traits::find::Find for gix_odb::Cache<S>>::try_find::h505b462e9cba2ee2
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gix-odb-0.69.1/src/cache.rs:163
     161 │     {
     162 │         fn try_find<'a>(&self, id: &oid, buffer: &'a mut Vec<u8>) -> Result<Option<Data<'a>>, gix_object::find::Error> {
     163 >             gix_pack::Find::try_find(self, id, buffer).map(|t| t.map(|t| t.0))
     164 │         }
     165 │     }
18: <gix_odb::memory::Proxy<T> as gix_object::traits::find::Find>::try_find::h40b6c57e98500767
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gix-odb-0.69.1/src/memory.rs:159
     157 │             }
     158 │         }
     159 >         self.inner.try_find(id, buffer)
     160 │     }
     161 │ }
19: gix_object::traits::find::ext::FindExt::find::h0ce666a910c19e76
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gix-object-0.49.1/src/traits/find.rs:234
     232 │             buffer: &'a mut Vec<u8>,
     233 │         ) -> Result<crate::Data<'a>, find::existing::Error> {
     234 >             self.try_find(id, buffer)
     235 │                 .map_err(find::existing::Error::Find)?
     236 │                 .ok_or_else(|| find::existing::Error::NotFound { oid: id.to_owned() })
20: gix::repository::object::<impl gix::types::Repository>::find_object::hb284647272300f9e
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gix-0.72.1/src/repository/object.rs:53
      51 │         }
      52 │         let mut buf = self.free_buf();
      53 >         let kind = self.objects.find(&id, &mut buf)?.kind;
      54 │         Ok(Object::from_data(id, kind, buf, self))
      55 │     }
21: <noseyparker_cli::cmd_scan::GitRepoResultIter as rayon::iter::ParallelIterator>::drive_unindexed::{{closure}}::{{closure}}::h2e81d9987823c008
    at /home/blarsen/projects/noseyparkerplusplus/crates/noseyparker-cli/src/cmd_scan.rs:199
     197 │
     198 │                     let blob = || -> Result<Blob> {
     199 >                         let mut blob = repo.find_object(blob_id)?.try_into_blob()?;
     200 │                         let data = std::mem::take(&mut blob.data); // avoid a copy
     201 │                         Ok(Blob::new(BlobId::from(&blob_id), data))
22: <noseyparker_cli::cmd_scan::GitRepoResultIter as rayon::iter::ParallelIterator>::drive_unindexed::{{closure}}::hcfc65172c38b96e7
    at /home/blarsen/projects/noseyparkerplusplus/crates/noseyparker-cli/src/cmd_scan.rs:198
     196 │                     let blob_id = md.blob_oid;
     197 │
     198 >                     let blob = || -> Result<Blob> {
     199 │                         let mut blob = repo.find_object(blob_id)?.try_into_blob()?;
     200 │                         let data = std::mem::take(&mut blob.data); // avoid a copy
23: core::ops::function::impls::<impl core::ops::function::Fn<A> for &F>::call::h04920647f8c3f68a
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:262
     260 │     {
     261 │         extern "rust-call" fn call(&self, args: A) -> F::Output {
     262 >             (**self).call(args)
     263 │         }
     264 │     }
24: <rayon::iter::map_with::MapWithFolder<C,U,F> as rayon::iter::plumbing::Folder<T>>::consume_iter::with::{{closure}}::hce48ffd6a09956f4
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/map_with.rs:317
     315 │             map_op: impl Fn(&mut U, T) -> R + 'f,
     316 │         ) -> impl FnMut(T) -> R + 'f {
     317 >             move |x| map_op(item, x)
     318 │         }
     319 │
25: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &mut F>::call_once::h8cf6aabb49d00555
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:305
     303 │         type Output = F::Output;
     304 │         extern "rust-call" fn call_once(self, args: A) -> F::Output {
     305 >             (*self).call_mut(args)
     306 │         }
     307 │     }
26: core::option::Option<T>::map::he9fed2335951ae0c
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:1113
    1111 │     {
    1112 │         match self {
    1113 >             Some(x) => Some(f(x)),
    1114 │             None => None,
    1115 │         }
27: <core::iter::adapters::map::Map<I,F> as core::iter::traits::iterator::Iterator>::next::ha3fb456556810b5c
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/adapters/map.rs:107
     105 │     #[inline]
     106 │     fn next(&mut self) -> Option<B> {
     107 >         self.iter.next().map(&mut self.f)
     108 │     }
     109 │
28: <core::iter::adapters::map::Map<I,F> as core::iter::traits::iterator::Iterator>::next::ha7f60e1c0a324d21
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/adapters/map.rs:107
     105 │     #[inline]
     106 │     fn next(&mut self) -> Option<B> {
     107 >         self.iter.next().map(&mut self.f)
     108 │     }
     109 │
29: rayon::iter::plumbing::Folder::consume_iter::hf8e09de90f9ea415
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/plumbing/mod.rs:177
     175 │         I: IntoIterator<Item = Item>,
     176 │     {
     177 >         for item in iter {
     178 │             self = self.consume(item);
     179 │             if self.full() {
30: <rayon::iter::map_with::MapWithFolder<C,U,F> as rayon::iter::plumbing::Folder<T>>::consume_iter::hcb8661039dd1a0f9
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/map_with.rs:322
     320 │         {
     321 │             let mapped_iter = iter.into_iter().map(with(&mut self.item, self.map_op));
     322 >             self.base = self.base.consume_iter(mapped_iter);
     323 │         }
     324 │         self
31: <rayon::iter::map_with::MapWithFolder<C,U,F> as rayon::iter::plumbing::Folder<T>>::consume_iter::hfd6b6a675578c81f
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/map_with.rs:322
     320 │         {
     321 │             let mapped_iter = iter.into_iter().map(with(&mut self.item, self.map_op));
     322 >             self.base = self.base.consume_iter(mapped_iter);
     323 │         }
     324 │         self
32: rayon::iter::plumbing::Producer::fold_with::ha6e5cd69b027c08d
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/plumbing/mod.rs:109
     107 │         F: Folder<Self::Item>,
     108 │     {
     109 >         folder.consume_iter(self.into_iter())
     110 │     }
     111 │ }
33: <rayon::iter::len::MinLenProducer<P> as rayon::iter::plumbing::Producer>::fold_with::h8428a8c1bf7a486c
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/len.rs:134
     132 │         F: Folder<Self::Item>,
     133 │     {
     134 >         self.base.fold_with(folder)
     135 │     }
     136 │ }
34: rayon::iter::plumbing::bridge_producer_consumer::helper::h372f7337d5f6da17
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/plumbing/mod.rs:437
     435 │             reducer.reduce(left_result, right_result)
     436 │         } else {
     437 >             producer.fold_with(consumer.into_folder()).complete()
     438 │         }
     439 │     }
35: rayon::iter::plumbing::bridge_producer_consumer::helper::{{closure}}::h9aad568542e81dd4
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/plumbing/mod.rs:426
     424 │                 },
     425 │                 |context| {
     426 >                     helper(
     427 │                         len - mid,
     428 │                         context.migrated(),
36: rayon_core::join::join_context::call_b::{{closure}}::h2d4a355590d1944e
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/join/mod.rs:129
     127 │     #[inline]
     128 │     fn call_b<R>(f: impl FnOnce(FnContext) -> R) -> impl FnOnce(bool) -> R {
     129 >         move |migrated| f(FnContext::new(migrated))
     130 │     }
     131 │
37: rayon_core::job::StackJob<L,F,R>::run_inline::h4de831323c4e97e7
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/job.rs:102
     100 │
     101 │     pub(super) unsafe fn run_inline(self, stolen: bool) -> R {
     102 >         self.func.into_inner().unwrap()(stolen)
     103 │     }
     104 │
38: rayon_core::join::join_context::{{closure}}::h5822005cf64c68e4
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/join/mod.rs:159
     157 │                     //
     158 │                     // Note that this could panic, but it's ok if we unwind here.
     159 >                     let result_b = job_b.run_inline(injected);
     160 │                     return (result_a, result_b);
     161 │                 } else {
39: rayon_core::registry::in_worker::hb110d42b82230fbb
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/registry.rs:951
     949 │             // current thread, so we know the data structure won't be
     950 │             // invalidated until we return.
     951 >             op(&*owner_thread, false)
     952 │         } else {
     953 │             global_registry().in_worker(op)
40: rayon_core::join::join_context::hc0311f36f2a71c18
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/join/mod.rs:132
     130 │     }
     131 │
     132 >     registry::in_worker(|worker_thread, injected| unsafe {
     133 │         // Create virtual wrapper for task b; this all has to be
     134 │         // done here so that the stack frame can keep it all live
41: rayon::iter::plumbing::bridge_producer_consumer::helper::h372f7337d5f6da17
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/plumbing/mod.rs:415
     413 │             let (left_producer, right_producer) = producer.split_at(mid);
     414 │             let (left_consumer, right_consumer, reducer) = consumer.split_at(mid);
     415 >             let (left_result, right_result) = join_context(
     416 │                 |context| {
     417 │                     helper(
42: rayon::iter::plumbing::bridge_producer_consumer::h8f825ccc0043937b
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/plumbing/mod.rs:396
     394 │ {
     395 │     let splitter = LengthSplitter::new(producer.min_len(), producer.max_len(), len);
     396 >     return helper(len, false, splitter, producer, consumer);
     397 │
     398 │     fn helper<P, C>(
43: <rayon::iter::plumbing::bridge::Callback<C> as rayon::iter::plumbing::ProducerCallback<I>>::callback::h1abc936b73e245c6
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/plumbing/mod.rs:372
     370 │             P: Producer<Item = I>,
     371 │         {
     372 >             bridge_producer_consumer(self.len, producer, self.consumer)
     373 │         }
     374 │     }
44: <<rayon::iter::len::MinLen<I> as rayon::iter::IndexedParallelIterator>::with_producer::Callback<CB> as rayon::iter::plumbing::ProducerCallback<T>>::callback::h28a6ba45beb8316b
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/len.rs:83
      81 │                     min: self.min,
      82 │                 };
      83 >                 self.callback.callback(producer)
      84 │             }
      85 │         }
45: <rayon::vec::Drain<T> as rayon::iter::IndexedParallelIterator>::with_producer::h0cfbf39e5e74bcbf
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/vec.rs:147
     145 │
     146 │             // The producer will move or drop each item from the drained range.
     147 >             callback.callback(producer)
     148 │         }
     149 │     }
46: <rayon::vec::IntoIter<T> as rayon::iter::IndexedParallelIterator>::with_producer::hc0ffe17d25b11b5f
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/vec.rs:83
      81 │     {
      82 │         // Drain every item, and then the vector only needs to free its buffer.
      83 >         self.vec.par_drain(..).with_producer(callback)
      84 │     }
      85 │ }
47: <rayon::iter::len::MinLen<I> as rayon::iter::IndexedParallelIterator>::with_producer::h553f26dda48af215
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/len.rs:60
      58 │         CB: ProducerCallback<Self::Item>,
      59 │     {
      60 >         return self.base.with_producer(Callback {
      61 │             callback,
      62 │             min: self.min,
48: rayon::iter::plumbing::bridge::h773705a9be8800aa
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/plumbing/mod.rs:356
     354 │ {
     355 │     let len = par_iter.len();
     356 >     return par_iter.with_producer(Callback { len, consumer });
     357 │
     358 │     struct Callback<C> {
49: <rayon::iter::len::MinLen<I> as rayon::iter::ParallelIterator>::drive_unindexed::hffeef17194c9017c
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/len.rs:36
      34 │         C: UnindexedConsumer<Self::Item>,
      35 │     {
      36 >         bridge(self, consumer)
      37 │     }
      38 │
50: <rayon::iter::map_with::MapInit<I,INIT,F> as rayon::iter::ParallelIterator>::drive_unindexed::h94a2b6f49cb5515b
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/map_with.rs:382
     380 │     {
     381 │         let consumer1 = MapInitConsumer::new(consumer, &self.init, &self.map_op);
     382 >         self.base.drive_unindexed(consumer1)
     383 │     }
     384 │
51: <noseyparker_cli::cmd_scan::GitRepoResultIter as rayon::iter::ParallelIterator>::drive_unindexed::h8390c4732055d594
    at /home/blarsen/projects/noseyparkerplusplus/crates/noseyparker-cli/src/cmd_scan.rs:176
     174 │         let repo = self.inner.repository.into_sync();
     175 │         let repo_path = Arc::new(self.inner.path.clone());
     176 >         self.inner
     177 │             .blobs
     178 │             .into_par_iter()
52: <noseyparker_cli::cmd_scan::FoundInputIter as rayon::iter::ParallelIterator>::drive_unindexed::h49dd338b93298ca7
    at /home/blarsen/projects/noseyparkerplusplus/crates/noseyparker-cli/src/cmd_scan.rs:301
     299 │         match self {
     300 │             FoundInputIter::File(i) => i.drive_unindexed(consumer),
     301 >             FoundInputIter::GitRepo(i) => i.drive_unindexed(consumer),
     302 │             FoundInputIter::EnumeratorFile(i) => i.drive_unindexed(consumer),
     303 │         }
53: <rayon::iter::flatten::FlattenFolder<C,<C as rayon::iter::plumbing::Consumer<<T as rayon::iter::IntoParallelIterator>::Item>>::Result> as rayon::iter::plumbing::Folder<T>>::consume::h3bcfa490e60c0810
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/flatten.rs:114
     112 │         let par_iter = item.into_par_iter();
     113 │         let consumer = self.base.split_off_left();
     114 >         let result = par_iter.drive_unindexed(consumer);
     115 │
     116 │         let previous = match self.previous {
54: <rayon::iter::filter_map::FilterMapFolder<C,P> as rayon::iter::plumbing::Folder<T>>::consume::hc8806136aef5923b
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/filter_map.rs:124
     122 │         let filter_op = self.filter_op;
     123 │         if let Some(mapped_item) = filter_op(item) {
     124 >             let base = self.base.consume(mapped_item);
     125 │             FilterMapFolder { base, filter_op }
     126 │         } else {
55: <&rayon::iter::par_bridge::IterParallelProducer<Iter> as rayon::iter::plumbing::UnindexedProducer>::fold_with::h6426e9e9ba9e3af3
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/par_bridge.rs:145
     143 │                 if let Some(it) = iter.next() {
     144 │                     drop(iter);
     145 >                     folder = folder.consume(it);
     146 │                     if folder.full() {
     147 │                         return folder;
56: rayon::iter::plumbing::bridge_unindexed_producer_consumer::h2b035d48d0c4f326
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/plumbing/mod.rs:478
     476 │                 reducer.reduce(left_result, right_result)
     477 │             }
     478 >             (producer, None) => producer.fold_with(consumer.into_folder()).complete(),
     479 │         }
     480 │     } else {
57: rayon::iter::plumbing::bridge_unindexed_producer_consumer::{{closure}}::hb8fb6da870149c5b
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-1.10.0/src/iter/plumbing/mod.rs:473
     471 │                 let bridge = bridge_unindexed_producer_consumer;
     472 │                 let (left_result, right_result) = join_context(
     473 >                     |context| bridge(context.migrated(), splitter, left_producer, left_consumer),
     474 │                     |context| bridge(context.migrated(), splitter, right_producer, right_consumer),
     475 │                 );
58: rayon_core::join::join_context::call_a::{{closure}}::h66b269fbf4a554a7
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/join/mod.rs:124
     122 │     #[inline]
     123 │     fn call_a<R>(f: impl FnOnce(FnContext) -> R, injected: bool) -> impl FnOnce() -> R {
     124 >         move || f(FnContext::new(injected))
     125 │     }
     126 │
59: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::hca943e5c7c818128
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:272
     270 │     #[inline]
     271 │     extern "rust-call" fn call_once(self, _args: ()) -> R {
     272 >         (self.0)()
     273 │     }
     274 │ }
60: std::panicking::try::do_call::h3c325e65ad71efc0
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:557
     555 │             let data = &mut (*data);
     556 │             let f = ManuallyDrop::take(&mut data.f);
     557 >             data.r = ManuallyDrop::new(f());
     558 │         }
     559 │     }
61: __rust_try
    at <unknown source file>
62: std::panicking::try::h58b0fcdeaabdd12f
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:520
     518 │     // See their safety preconditions for more information
     519 │     unsafe {
     520 >         return if intrinsics::catch_unwind(do_call::<F, R>, data_ptr, do_catch::<F, R>) == 0 {
     521 │             Ok(ManuallyDrop::into_inner(data.r))
     522 │         } else {
63: std::panic::catch_unwind::hcd77458f398ee48c
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:358
     356 │ #[stable(feature = "catch_unwind", since = "1.9.0")]
     357 │ pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
     358 >     unsafe { panicking::r#try(f) }
     359 │ }
     360 │
64: rayon_core::unwind::halt_unwinding::h4c6f1ef13155c7d7
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/unwind.rs:17
      15 │     F: FnOnce() -> R,
      16 │ {
      17 >     panic::catch_unwind(AssertUnwindSafe(func))
      18 │ }
      19 │
65: rayon_core::join::join_context::{{closure}}::haeadd60ea8035103
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/join/mod.rs:142
     140 │
     141 │         // Execute task a; hopefully b gets stolen in the meantime.
     142 >         let status_a = unwind::halt_unwinding(call_a(oper_a, injected));
     143 │         let result_a = match status_a {
     144 │             Ok(v) => v,
66: rayon_core::registry::Registry::in_worker_cold::{{closure}}::{{closure}}::h4ffcd99f706ffada
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/registry.rs:522
     520 │                     let worker_thread = WorkerThread::current();
     521 │                     assert!(injected && !worker_thread.is_null());
     522 >                     op(&*worker_thread, true)
     523 │                 },
     524 │                 LatchRef::new(l),
67: rayon_core::job::JobResult<T>::call::{{closure}}::h2d8e738783f1af7f
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/job.rs:218
     216 │ impl<T> JobResult<T> {
     217 │     fn call(func: impl FnOnce(bool) -> T) -> Self {
     218 >         match unwind::halt_unwinding(|| func(true)) {
     219 │             Ok(x) => JobResult::Ok(x),
     220 │             Err(x) => JobResult::Panic(x),
68: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::h62dbad7b756c450e
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:272
     270 │     #[inline]
     271 │     extern "rust-call" fn call_once(self, _args: ()) -> R {
     272 >         (self.0)()
     273 │     }
     274 │ }
69: std::panicking::try::do_call::h10d264f94e4540fe
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:557
     555 │             let data = &mut (*data);
     556 │             let f = ManuallyDrop::take(&mut data.f);
     557 >             data.r = ManuallyDrop::new(f());
     558 │         }
     559 │     }
70: __rust_try
    at <unknown source file>
71: std::panicking::try::h6b62f24fc180418c
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:520
     518 │     // See their safety preconditions for more information
     519 │     unsafe {
     520 >         return if intrinsics::catch_unwind(do_call::<F, R>, data_ptr, do_catch::<F, R>) == 0 {
     521 │             Ok(ManuallyDrop::into_inner(data.r))
     522 │         } else {
72: std::panic::catch_unwind::hca78f5ba028ec6c3
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:358
     356 │ #[stable(feature = "catch_unwind", since = "1.9.0")]
     357 │ pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
     358 >     unsafe { panicking::r#try(f) }
     359 │ }
     360 │
73: rayon_core::unwind::halt_unwinding::hf2452592a011c996
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/unwind.rs:17
      15 │     F: FnOnce() -> R,
      16 │ {
      17 >     panic::catch_unwind(AssertUnwindSafe(func))
      18 │ }
      19 │
74: rayon_core::job::JobResult<T>::call::h6f3c0cd413514835
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/job.rs:218
     216 │ impl<T> JobResult<T> {
     217 │     fn call(func: impl FnOnce(bool) -> T) -> Self {
     218 >         match unwind::halt_unwinding(|| func(true)) {
     219 │             Ok(x) => JobResult::Ok(x),
     220 │             Err(x) => JobResult::Panic(x),
75: <rayon_core::job::StackJob<L,F,R> as rayon_core::job::Job>::execute::h684a52e81cc0409d
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/job.rs:120
     118 │         let abort = unwind::AbortIfPanic;
     119 │         let func = (*this.func.get()).take().unwrap();
     120 >         (*this.result.get()) = JobResult::call(func);
     121 │         Latch::set(&this.latch);
     122 │         mem::forget(abort);
76: rayon_core::job::JobRef::execute::ha601b4ad0234eefa
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/job.rs:64
      62 │     #[inline]
      63 │     pub(super) unsafe fn execute(self) {
      64 >         (self.execute_fn)(self.pointer)
      65 │     }
      66 │ }
77: rayon_core::registry::WorkerThread::execute::h303b24019f5d76f5
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/registry.rs:860
     858 │     #[inline]
     859 │     pub(super) unsafe fn execute(&self, job: JobRef) {
     860 >         job.execute();
     861 │     }
     862 │
78: rayon_core::registry::WorkerThread::wait_until_cold::h81dc9ddf37ae1fd2
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/registry.rs:794
     792 │                 if let Some(job) = self.find_work() {
     793 │                     self.registry.sleep.work_found();
     794 >                     self.execute(job);
     795 │                     // The job might have injected local work, so go back to the outer loop.
     796 │                     continue 'outer;
79: rayon_core::registry::WorkerThread::wait_until::h542fc7d746a67754
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/registry.rs:769
     767 │         let latch = latch.as_core_latch();
     768 │         if !latch.probe() {
     769 >             self.wait_until_cold(latch);
     770 │         }
     771 │     }
80: rayon_core::registry::WorkerThread::wait_until_out_of_work::h31529aeb7dda607b
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/registry.rs:818
     816 │         let index = self.index;
     817 │
     818 >         self.wait_until(&registry.thread_infos[index].terminate);
     819 │
     820 │         // Should not be any work left in our queue.
81: rayon_core::registry::main_loop::h71f1e250e38a88e2
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/registry.rs:923
     921 │     }
     922 │
     923 >     worker_thread.wait_until_out_of_work();
     924 │
     925 │     // Normal termination, do not abort.
82: rayon_core::registry::ThreadBuilder::run::h0d5c633bf4b630cc
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/registry.rs:53
      51 │     /// thread pool is dropped.
      52 │     pub fn run(self) {
      53 >         unsafe { main_loop(self) }
      54 │     }
      55 │ }
83: <rayon_core::registry::DefaultSpawn as rayon_core::registry::ThreadSpawn>::spawn::{{closure}}::h6e8a8fe4e34fd376
    at /home/blarsen/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rayon-core-1.12.1/src/registry.rs:98
      96 │             b = b.stack_size(stack_size);
      97 │         }
      98 >         b.spawn(|| thread.run())?;
      99 │         Ok(())
     100 │     }
84: std::sys::backtrace::__rust_begin_short_backtrace::ha240e7ef2440fa50
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:154
     152 │     F: FnOnce() -> T,
     153 │ {
     154 >     let result = f();
     155 │
     156 │     // prevent this frame from being tail-call optimised away
85: std::thread::Builder::spawn_unchecked_::{{closure}}::{{closure}}::h638b8c0b1d37c2e1
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/mod.rs:561
     559 │             let try_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
     560 │                 crate::sys::backtrace::__rust_begin_short_backtrace(|| hooks.run());
     561 >                 crate::sys::backtrace::__rust_begin_short_backtrace(f)
     562 │             }));
     563 │             // SAFETY: `their_packet` as been built just above and moved by the
86: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::h2f63e73aa378b5e9
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:272
     270 │     #[inline]
     271 │     extern "rust-call" fn call_once(self, _args: ()) -> R {
     272 >         (self.0)()
     273 │     }
     274 │ }
87: std::panicking::try::do_call::h6d6d8a3d23323aad
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:557
     555 │             let data = &mut (*data);
     556 │             let f = ManuallyDrop::take(&mut data.f);
     557 >             data.r = ManuallyDrop::new(f());
     558 │         }
     559 │     }
88: __rust_try
    at <unknown source file>
89: std::panicking::try::hec4f5105d251e24f
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:520
     518 │     // See their safety preconditions for more information
     519 │     unsafe {
     520 >         return if intrinsics::catch_unwind(do_call::<F, R>, data_ptr, do_catch::<F, R>) == 0 {
     521 │             Ok(ManuallyDrop::into_inner(data.r))
     522 │         } else {
90: std::panic::catch_unwind::h54574b824fc683a3
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:358
     356 │ #[stable(feature = "catch_unwind", since = "1.9.0")]
     357 │ pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
     358 >     unsafe { panicking::r#try(f) }
     359 │ }
     360 │
91: std::thread::Builder::spawn_unchecked_::{{closure}}::h5561b49cc0050c06
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/mod.rs:559
     557 │
     558 │             let f = f.into_inner();
     559 >             let try_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
     560 │                 crate::sys::backtrace::__rust_begin_short_backtrace(|| hooks.run());
     561 │                 crate::sys::backtrace::__rust_begin_short_backtrace(f)
92: core::ops::function::FnOnce::call_once{{vtable.shim}}::h6cc0d940ae51894e
    at /home/blarsen/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250
     248 │     /// Performs the call operation.
     249 │     #[unstable(feature = "fn_traits", issue = "29625")]
     250 >     extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
     251 │ }
     252 │
93: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h9578f6ea1d4e1c4b
    at /rustc/9fc6b43126469e3858e2fe86cafb4f0fd5068869/library/alloc/src/boxed.rs:1972
94: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::hf4a2f438d8019348
    at /rustc/9fc6b43126469e3858e2fe86cafb4f0fd5068869/library/alloc/src/boxed.rs:1972
95: std::sys::pal::unix::thread::Thread::new::thread_start::h14f1eb868ff90fc9
    at /rustc/9fc6b43126469e3858e2fe86cafb4f0fd5068869/library/std/src/sys/pal/unix/thread.rs:105
96: start_thread
    at ./nptl/pthread_create.c:442
97: __GI___clone3
    at ./misc/../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

What I expect

gitoxide code should not crash, even if provided malformed input

The crashing gitoxide code

pub fn apply(base: &[u8], mut target: &mut [u8], data: &[u8]) {
let mut i = 0;
while let Some(cmd) = data.get(i) {
i += 1;
match cmd {
cmd if cmd & 0b1000_0000 != 0 => {
let (mut ofs, mut size): (u32, u32) = (0, 0);
if cmd & 0b0000_0001 != 0 {
ofs = u32::from(data[i]);
i += 1;
}
if cmd & 0b0000_0010 != 0 {
ofs |= u32::from(data[i]) << 8;
i += 1;
}
if cmd & 0b0000_0100 != 0 {
ofs |= u32::from(data[i]) << 16;
i += 1;
}
if cmd & 0b0000_1000 != 0 {
ofs |= u32::from(data[i]) << 24;
i += 1;
}
if cmd & 0b0001_0000 != 0 {
size = u32::from(data[i]);
i += 1;
}
if cmd & 0b0010_0000 != 0 {
size |= u32::from(data[i]) << 8;
i += 1;
}
if cmd & 0b0100_0000 != 0 {
size |= u32::from(data[i]) << 16;
i += 1;
}
if size == 0 {
size = 0x10000; // 65536
}
let ofs = ofs as usize;
std::io::Write::write(&mut target, &base[ofs..ofs + size as usize])
.expect("delta copy from base: byte slices must match");
}
0 => panic!("encountered unsupported command code: 0"),
size => {
std::io::Write::write(&mut target, &data[i..i + *size as usize])
.expect("delta copy data: slice sizes to match up");
i += *size as usize;
}
}
}
assert_eq!(i, data.len());
assert_eq!(target.len(), 0);
}

In particular, it's the panic!("encountered unsupported command code: 0") code that I'm seeing crash. However, there is also an expect call just above that which also could be triggered by ill-formed user-controllable input.

FYI, looking at the git blame for that code, it appears to be pretty old, much of it from 2021.

Reproducing this

The input repository to Nosey Parker (that's triggering the gitoxide crash when enumerating inputs) is elongbug/deqp, cloned with git clone --bare https://github.com/elongbug/deqp.git. The last changes there are from 10 years ago.

The packfile contents that I see:

% shasum deqp.git/objects/pack/*
77809f4a1783bc011dd9f49c0ee18fe45d0cb792  deqp.git/objects/pack/pack-d57c91f4836f96358552c59966566d6a8f522440.idx
8325905c1a6b262deaadf6c6a1a9ede804859d00  deqp.git/objects/pack/pack-d57c91f4836f96358552c59966566d6a8f522440.pack

I see the crash deterministically, with or without multithreading in Nosey Parker, but only on an x86_64 machine, and not on Apple Silicon-based machines (i.e., ARM).

Other notes

I see the gix_pack::data::delta::apply function used only in 3 places in gitoxide, and all 3 locations return Result values already. Could gix_pack::data::delta::apply be modified to return an error instead of panicking on malformed input? Would you like a PR for this? It might not be a semver-breaking change, as that function looks like it's only exposed pub(crate) from the gix_pack::data module.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions