From 3fc2cab9ac30e29543604072e6752cedf619af1f Mon Sep 17 00:00:00 2001 From: subotac <73706465+subotac@users.noreply.github.com> Date: Sat, 1 Aug 2026 12:43:49 +0300 Subject: [PATCH 1/2] Avoid overflow in component ABI size calculations --- crates/environ/src/component/types.rs | 86 ++++++++++++++++++++------- 1 file changed, 65 insertions(+), 21 deletions(-) diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index 18d37a429688..47aee8e2cd72 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -676,7 +676,10 @@ impl Default for CanonicalAbiInfo { const fn align_to(a: u32, b: u32) -> u32 { assert!(b.is_power_of_two()); - (a + (b - 1)) & !(b - 1) + match a.checked_add(b - 1) { + Some(a) => a & !(b - 1), + None => u32::MAX, + } } const fn max(a: u32, b: u32) -> u32 { @@ -728,9 +731,9 @@ impl CanonicalAbiInfo { let mut ret = CanonicalAbiInfo::default(); for field in fields { - ret.size32 = align_to(ret.size32, field.align32) + field.size32; + ret.size32 = align_to(ret.size32, field.align32).saturating_add(field.size32); ret.align32 = ret.align32.max(field.align32); - ret.size64 = align_to(ret.size64, field.align64) + field.size64; + ret.size64 = align_to(ret.size64, field.align64).saturating_add(field.size64); ret.align64 = ret.align64.max(field.align64); ret.flat_count = add_flat(ret.flat_count, field.flat_count); } @@ -748,9 +751,9 @@ impl CanonicalAbiInfo { let mut i = 0; while i < fields.len() { let field = &fields[i]; - ret.size32 = align_to(ret.size32, field.align32) + field.size32; + ret.size32 = align_to(ret.size32, field.align32).saturating_add(field.size32); ret.align32 = max(ret.align32, field.align32); - ret.size64 = align_to(ret.size64, field.align64) + field.size64; + ret.size64 = align_to(ret.size64, field.align64).saturating_add(field.size64); ret.align64 = max(ret.align64, field.align64); ret.flat_count = add_flat(ret.flat_count, field.flat_count); i += 1; @@ -805,31 +808,31 @@ impl CanonicalAbiInfo { /// Returns the delta from the current value of `offset` to align properly /// and read the next record field of type `abi` for 32-bit memories. pub fn next_field32(&self, offset: &mut u32) -> u32 { - *offset = align_to(*offset, self.align32) + self.size32; - *offset - self.size32 + let start = align_to(*offset, self.align32); + *offset = start.saturating_add(self.size32); + start } /// Same as `next_field32`, but bumps a usize pointer pub fn next_field32_size(&self, offset: &mut usize) -> usize { - let cur = u32::try_from(*offset).unwrap(); - let cur = align_to(cur, self.align32) + self.size32; - *offset = usize::try_from(cur).unwrap(); - usize::try_from(cur - self.size32).unwrap() + let start = align_to(u32::try_from(*offset).unwrap(), self.align32); + *offset = usize::try_from(start.saturating_add(self.size32)).unwrap(); + usize::try_from(start).unwrap() } /// Returns the delta from the current value of `offset` to align properly /// and read the next record field of type `abi` for 64-bit memories. pub fn next_field64(&self, offset: &mut u32) -> u32 { - *offset = align_to(*offset, self.align64) + self.size64; - *offset - self.size64 + let start = align_to(*offset, self.align64); + *offset = start.saturating_add(self.size64); + start } /// Same as `next_field64`, but bumps a usize pointer pub fn next_field64_size(&self, offset: &mut usize) -> usize { - let cur = u32::try_from(*offset).unwrap(); - let cur = align_to(cur, self.align64) + self.size64; - *offset = usize::try_from(cur).unwrap(); - usize::try_from(cur - self.size64).unwrap() + let start = align_to(u32::try_from(*offset).unwrap(), self.align64); + *offset = usize::try_from(start.saturating_add(self.size64)).unwrap(); + usize::try_from(start).unwrap() } /// Returns ABI information for a structure which contains `count` flags. @@ -875,12 +878,12 @@ impl CanonicalAbiInfo { } CanonicalAbiInfo { size32: align_to( - align_to(discrim_size, max_align32) + max_size32, + align_to(discrim_size, max_align32).saturating_add(max_size32), max_align32, ), align32: max_align32, size64: align_to( - align_to(discrim_size, max_align64) + max_size64, + align_to(discrim_size, max_align64).saturating_add(max_size64), max_align64, ), align64: max_align64, @@ -916,12 +919,12 @@ impl CanonicalAbiInfo { } CanonicalAbiInfo { size32: align_to( - align_to(discrim_size, max_align32) + max_size32, + align_to(discrim_size, max_align32).saturating_add(max_size32), max_align32, ), align32: max_align32, size64: align_to( - align_to(discrim_size, max_align64) + max_size64, + align_to(discrim_size, max_align64).saturating_add(max_size64), max_align64, ), align64: max_align64, @@ -1453,4 +1456,45 @@ mod tests { assert_ne!(a, b); assert_eq!(a, a.clone()); } + + #[test] + fn canonical_abi_size_overflow_saturates() { + const LARGE: CanonicalAbiInfo = + CanonicalAbiInfo::fixed_length_list_static(&CanonicalAbiInfo::SCALAR8, 1 << 30); + const RECORD: CanonicalAbiInfo = CanonicalAbiInfo::record_static(&[LARGE, LARGE]); + const VARIANT: CanonicalAbiInfo = CanonicalAbiInfo::variant_static(&[Some(LARGE), None]); + + assert_eq!(LARGE.size32, u32::MAX); + assert_eq!(LARGE.size64, u32::MAX); + + let record = CanonicalAbiInfo::record([&LARGE, &LARGE].into_iter()); + assert_eq!(record.size32, u32::MAX); + assert_eq!(record.size64, u32::MAX); + assert_eq!(RECORD.size32, u32::MAX); + assert_eq!(RECORD.size64, u32::MAX); + + let variant = CanonicalAbiInfo::variant([Some(&LARGE), None]); + assert_eq!(variant.size32, u32::MAX); + assert_eq!(variant.size64, u32::MAX); + assert_eq!(VARIANT.size32, u32::MAX); + assert_eq!(VARIANT.size64, u32::MAX); + + let mut offset32 = 8; + assert_eq!(LARGE.next_field32(&mut offset32), 8); + assert_eq!(offset32, u32::MAX); + assert_eq!( + CanonicalAbiInfo::SCALAR8.next_field32(&mut offset32), + u32::MAX + ); + assert_eq!(offset32, u32::MAX); + + let mut offset64 = 8; + assert_eq!(LARGE.next_field64(&mut offset64), 8); + assert_eq!(offset64, u32::MAX); + assert_eq!( + CanonicalAbiInfo::SCALAR8.next_field64(&mut offset64), + u32::MAX + ); + assert_eq!(offset64, u32::MAX); + } } From 3ca57ddca9954da744dad642cb771547dde44130 Mon Sep 17 00:00:00 2001 From: subotac <73706465+subotac@users.noreply.github.com> Date: Mon, 3 Aug 2026 19:59:11 +0300 Subject: [PATCH 2/2] Narrow canonical ABI overflow handling Signed-off-by: subotac <73706465+subotac@users.noreply.github.com> --- crates/environ/src/component/types.rs | 122 +++++++----------- .../component-model/fixed_length_lists.wast | 11 ++ 2 files changed, 59 insertions(+), 74 deletions(-) diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index 47aee8e2cd72..bdc7efe32b2b 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -645,11 +645,11 @@ pub enum InterfaceType { /// memory32 and memory64-based types. #[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] pub struct CanonicalAbiInfo { - /// The byte-size of this type in a 32-bit memory. + /// The byte-size of this type in a 32-bit memory, saturated at [`u32::MAX`]. pub size32: u32, /// The byte-alignment of this type in a 32-bit memory. pub align32: u32, - /// The byte-size of this type in a 64-bit memory. + /// The byte-size of this type in a 64-bit memory, saturated at [`u32::MAX`]. pub size64: u32, /// The byte-alignment of this type in a 64-bit memory. pub align64: u32, @@ -676,12 +676,27 @@ impl Default for CanonicalAbiInfo { const fn align_to(a: u32, b: u32) -> u32 { assert!(b.is_power_of_two()); - match a.checked_add(b - 1) { - Some(a) => a & !(b - 1), - None => u32::MAX, + (a + (b - 1)) & !(b - 1) +} + +const fn saturating_size(size: u64) -> u32 { + if size > u32::MAX as u64 { + u32::MAX + } else { + size as u32 } } +const fn align_size_to(size: u32, align: u32) -> u32 { + assert!(align.is_power_of_two()); + let align = align as u64; + saturating_size((size as u64 + (align - 1)) & !(align - 1)) +} + +const fn add_sizes(a: u32, b: u32) -> u32 { + saturating_size(a as u64 + b as u64) +} + const fn max(a: u32, b: u32) -> u32 { if a > b { a } else { b } } @@ -731,14 +746,14 @@ impl CanonicalAbiInfo { let mut ret = CanonicalAbiInfo::default(); for field in fields { - ret.size32 = align_to(ret.size32, field.align32).saturating_add(field.size32); + ret.size32 = add_sizes(align_size_to(ret.size32, field.align32), field.size32); ret.align32 = ret.align32.max(field.align32); - ret.size64 = align_to(ret.size64, field.align64).saturating_add(field.size64); + ret.size64 = add_sizes(align_size_to(ret.size64, field.align64), field.size64); ret.align64 = ret.align64.max(field.align64); ret.flat_count = add_flat(ret.flat_count, field.flat_count); } - ret.size32 = align_to(ret.size32, ret.align32); - ret.size64 = align_to(ret.size64, ret.align64); + ret.size32 = align_size_to(ret.size32, ret.align32); + ret.size64 = align_size_to(ret.size64, ret.align64); return ret; } @@ -751,15 +766,15 @@ impl CanonicalAbiInfo { let mut i = 0; while i < fields.len() { let field = &fields[i]; - ret.size32 = align_to(ret.size32, field.align32).saturating_add(field.size32); + ret.size32 = add_sizes(align_size_to(ret.size32, field.align32), field.size32); ret.align32 = max(ret.align32, field.align32); - ret.size64 = align_to(ret.size64, field.align64).saturating_add(field.size64); + ret.size64 = add_sizes(align_size_to(ret.size64, field.align64), field.size64); ret.align64 = max(ret.align64, field.align64); ret.flat_count = add_flat(ret.flat_count, field.flat_count); i += 1; } - ret.size32 = align_to(ret.size32, ret.align32); - ret.size64 = align_to(ret.size64, ret.align64); + ret.size32 = align_size_to(ret.size32, ret.align32); + ret.size64 = align_size_to(ret.size64, ret.align64); return ret; } @@ -808,31 +823,31 @@ impl CanonicalAbiInfo { /// Returns the delta from the current value of `offset` to align properly /// and read the next record field of type `abi` for 32-bit memories. pub fn next_field32(&self, offset: &mut u32) -> u32 { - let start = align_to(*offset, self.align32); - *offset = start.saturating_add(self.size32); - start + *offset = align_to(*offset, self.align32) + self.size32; + *offset - self.size32 } /// Same as `next_field32`, but bumps a usize pointer pub fn next_field32_size(&self, offset: &mut usize) -> usize { - let start = align_to(u32::try_from(*offset).unwrap(), self.align32); - *offset = usize::try_from(start.saturating_add(self.size32)).unwrap(); - usize::try_from(start).unwrap() + let cur = u32::try_from(*offset).unwrap(); + let cur = align_to(cur, self.align32) + self.size32; + *offset = usize::try_from(cur).unwrap(); + usize::try_from(cur - self.size32).unwrap() } /// Returns the delta from the current value of `offset` to align properly /// and read the next record field of type `abi` for 64-bit memories. pub fn next_field64(&self, offset: &mut u32) -> u32 { - let start = align_to(*offset, self.align64); - *offset = start.saturating_add(self.size64); - start + *offset = align_to(*offset, self.align64) + self.size64; + *offset - self.size64 } /// Same as `next_field64`, but bumps a usize pointer pub fn next_field64_size(&self, offset: &mut usize) -> usize { - let start = align_to(u32::try_from(*offset).unwrap(), self.align64); - *offset = usize::try_from(start.saturating_add(self.size64)).unwrap(); - usize::try_from(start).unwrap() + let cur = u32::try_from(*offset).unwrap(); + let cur = align_to(cur, self.align64) + self.size64; + *offset = usize::try_from(cur).unwrap(); + usize::try_from(cur - self.size64).unwrap() } /// Returns ABI information for a structure which contains `count` flags. @@ -877,13 +892,13 @@ impl CanonicalAbiInfo { } } CanonicalAbiInfo { - size32: align_to( - align_to(discrim_size, max_align32).saturating_add(max_size32), + size32: align_size_to( + add_sizes(align_size_to(discrim_size, max_align32), max_size32), max_align32, ), align32: max_align32, - size64: align_to( - align_to(discrim_size, max_align64).saturating_add(max_size64), + size64: align_size_to( + add_sizes(align_size_to(discrim_size, max_align64), max_size64), max_align64, ), align64: max_align64, @@ -918,13 +933,13 @@ impl CanonicalAbiInfo { i += 1; } CanonicalAbiInfo { - size32: align_to( - align_to(discrim_size, max_align32).saturating_add(max_size32), + size32: align_size_to( + add_sizes(align_size_to(discrim_size, max_align32), max_size32), max_align32, ), align32: max_align32, - size64: align_to( - align_to(discrim_size, max_align64).saturating_add(max_size64), + size64: align_size_to( + add_sizes(align_size_to(discrim_size, max_align64), max_size64), max_align64, ), align64: max_align64, @@ -1456,45 +1471,4 @@ mod tests { assert_ne!(a, b); assert_eq!(a, a.clone()); } - - #[test] - fn canonical_abi_size_overflow_saturates() { - const LARGE: CanonicalAbiInfo = - CanonicalAbiInfo::fixed_length_list_static(&CanonicalAbiInfo::SCALAR8, 1 << 30); - const RECORD: CanonicalAbiInfo = CanonicalAbiInfo::record_static(&[LARGE, LARGE]); - const VARIANT: CanonicalAbiInfo = CanonicalAbiInfo::variant_static(&[Some(LARGE), None]); - - assert_eq!(LARGE.size32, u32::MAX); - assert_eq!(LARGE.size64, u32::MAX); - - let record = CanonicalAbiInfo::record([&LARGE, &LARGE].into_iter()); - assert_eq!(record.size32, u32::MAX); - assert_eq!(record.size64, u32::MAX); - assert_eq!(RECORD.size32, u32::MAX); - assert_eq!(RECORD.size64, u32::MAX); - - let variant = CanonicalAbiInfo::variant([Some(&LARGE), None]); - assert_eq!(variant.size32, u32::MAX); - assert_eq!(variant.size64, u32::MAX); - assert_eq!(VARIANT.size32, u32::MAX); - assert_eq!(VARIANT.size64, u32::MAX); - - let mut offset32 = 8; - assert_eq!(LARGE.next_field32(&mut offset32), 8); - assert_eq!(offset32, u32::MAX); - assert_eq!( - CanonicalAbiInfo::SCALAR8.next_field32(&mut offset32), - u32::MAX - ); - assert_eq!(offset32, u32::MAX); - - let mut offset64 = 8; - assert_eq!(LARGE.next_field64(&mut offset64), 8); - assert_eq!(offset64, u32::MAX); - assert_eq!( - CanonicalAbiInfo::SCALAR8.next_field64(&mut offset64), - u32::MAX - ); - assert_eq!(offset64, u32::MAX); - } } diff --git a/tests/misc_testsuite/component-model/fixed_length_lists.wast b/tests/misc_testsuite/component-model/fixed_length_lists.wast index 9e1d76563bd0..ca6054be251b 100644 --- a/tests/misc_testsuite/component-model/fixed_length_lists.wast +++ b/tests/misc_testsuite/component-model/fixed_length_lists.wast @@ -15,6 +15,17 @@ ;; Every mismatch increases the return value by 1. +;; Large fixed-length lists saturate cached ABI sizes instead of overflowing +;; when nested in a record. +(component + (component + (type $l (list u64 1073741824)) + (type $t (tuple $l $l)) + (type $f (func (param "a" $t))) + (import "x" (func (type $f))) + ) +) + (component (component (;0;) (type $ty-test:fixed-size-lists/to-test (;0;)