Skip to content

Commit 942e59e

Browse files
[naga] Enforce a maximum size for any type (gfx-rs#7950)
* [naga] Enforce a maximum size for any type Fixes gfx-rs#4339 Fixes gfx-rs#7383 * Doc fix and changelog entry
1 parent 2c81896 commit 942e59e

8 files changed

Lines changed: 266 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ We have merged the acceleration structure feature into the `RayQuery` feature. T
5353

5454
By @Vecvec in [#7913](https://github.com/gfx-rs/wgpu/pull/7913).
5555

56+
### Changes
57+
58+
#### Naga
59+
60+
Naga now requires that no type be larger than 1 GB. This limit may be lowered in the future; feedback on an appropriate value for the limit is welcome. By @andyleiserson in [#7950](https://github.com/gfx-rs/wgpu/pull/7950).
61+
5662
## v26.0.1 (2025-07-10)
5763

5864
### Bug Fixes

naga/src/front/wgsl/error.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,12 @@ pub(crate) enum Error<'a> {
409409
accept_span: Span,
410410
accept_type: String,
411411
},
412+
StructMemberTooLarge {
413+
member_name_span: Span,
414+
},
415+
TypeTooLarge {
416+
span: Span,
417+
},
412418
}
413419

414420
impl From<ConflictingDiagnosticRuleError> for Error<'_> {
@@ -1367,6 +1373,22 @@ impl<'a> Error<'a> {
13671373
],
13681374
notes: vec![],
13691375
},
1376+
Error::StructMemberTooLarge { member_name_span } => ParseError {
1377+
message: "struct member is too large".into(),
1378+
labels: vec![(member_name_span, "this member exceeds the maximum size".into())],
1379+
notes: vec![format!(
1380+
"the maximum size is {} bytes",
1381+
crate::valid::MAX_TYPE_SIZE
1382+
)],
1383+
},
1384+
Error::TypeTooLarge { span } => ParseError {
1385+
message: "type is too large".into(),
1386+
labels: vec![(span, "this type exceeds the maximum size".into())],
1387+
notes: vec![format!(
1388+
"the maximum size is {} bytes",
1389+
crate::valid::MAX_TYPE_SIZE
1390+
)],
1391+
},
13701392
}
13711393
}
13721394
}

naga/src/front/wgsl/lower/mod.rs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use alloc::{
77
};
88
use core::num::NonZeroU32;
99

10-
use crate::common::ForDebugWithTypes;
1110
use crate::front::wgsl::error::{Error, ExpectedToken, InvalidAssignmentType};
1211
use crate::front::wgsl::index::Index;
1312
use crate::front::wgsl::parse::number::Number;
@@ -18,6 +17,7 @@ use crate::{
1817
common::wgsl::{TryToWgsl, TypeContext},
1918
compact::KeepUnused,
2019
};
20+
use crate::{common::ForDebugWithTypes, proc::LayoutErrorInner};
2121
use crate::{ir, proc};
2222
use crate::{Arena, FastHashMap, FastIndexMap, Handle, Span};
2323

@@ -3709,7 +3709,27 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
37093709
for member in s.members.iter() {
37103710
let ty = self.resolve_ast_type(member.ty, &mut ctx.as_const())?;
37113711

3712-
ctx.layouter.update(ctx.module.to_ctx()).unwrap();
3712+
ctx.layouter.update(ctx.module.to_ctx()).map_err(|err| {
3713+
let LayoutErrorInner::TooLarge = err.inner else {
3714+
unreachable!("unexpected layout error: {err:?}");
3715+
};
3716+
// Since anonymous types of struct members don't get a span,
3717+
// associate the error with the member. The layouter could have
3718+
// failed on any type that was pending layout, but if it wasn't
3719+
// the current struct member, it wasn't a struct member at all,
3720+
// because we resolve struct members one-by-one.
3721+
if ty == err.ty {
3722+
Box::new(Error::StructMemberTooLarge {
3723+
member_name_span: member.name.span,
3724+
})
3725+
} else {
3726+
// Lots of type definitions don't get spans, so this error
3727+
// message may not be very useful.
3728+
Box::new(Error::TypeTooLarge {
3729+
span: ctx.module.types.get_span(err.ty),
3730+
})
3731+
}
3732+
})?;
37133733

37143734
let member_min_size = ctx.layouter[ty].size;
37153735
let member_min_alignment = ctx.layouter[ty].alignment;
@@ -3761,6 +3781,9 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
37613781
});
37623782

37633783
offset += member_size;
3784+
if offset > crate::valid::MAX_TYPE_SIZE {
3785+
return Err(Box::new(Error::TypeTooLarge { span }));
3786+
}
37643787
}
37653788

37663789
let size = struct_alignment.round_up(offset);
@@ -3938,7 +3961,17 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
39383961
let base = self.resolve_ast_type(base, &mut ctx.as_const())?;
39393962
let size = self.array_size(size, ctx)?;
39403963

3941-
ctx.layouter.update(ctx.module.to_ctx()).unwrap();
3964+
// Determine the size of the base type, if needed.
3965+
ctx.layouter.update(ctx.module.to_ctx()).map_err(|err| {
3966+
let LayoutErrorInner::TooLarge = err.inner else {
3967+
unreachable!("unexpected layout error: {err:?}");
3968+
};
3969+
// Lots of type definitions don't get spans, so this error
3970+
// message may not be very useful.
3971+
Box::new(Error::TypeTooLarge {
3972+
span: ctx.module.types.get_span(err.ty),
3973+
})
3974+
})?;
39423975
let stride = ctx.layouter[base].to_stride();
39433976

39443977
ir::TypeInner::Array { base, size, stride }

naga/src/ir/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,18 @@ impl VectorSize {
411411
pub const MAX: usize = Self::Quad as usize;
412412
}
413413

414+
impl From<VectorSize> for u8 {
415+
fn from(size: VectorSize) -> u8 {
416+
size as u8
417+
}
418+
}
419+
420+
impl From<VectorSize> for u32 {
421+
fn from(size: VectorSize) -> u32 {
422+
size as u32
423+
}
424+
}
425+
414426
/// Primitive type for a scalar.
415427
#[repr(u8)]
416428
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]

naga/src/proc/layouter.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use core::{fmt::Display, num::NonZeroU32, ops};
22

3-
use crate::arena::{Handle, HandleVec};
3+
use crate::{
4+
arena::{Handle, HandleVec},
5+
valid::MAX_TYPE_SIZE,
6+
};
47

58
/// A newtype struct where its only valid values are powers of 2
69
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
@@ -121,6 +124,12 @@ impl ops::Index<Handle<crate::Type>> for Layouter {
121124
}
122125
}
123126

127+
/// Errors generated by the `Layouter`.
128+
///
129+
/// All of these errors can be produced when validating an arbitrary module.
130+
/// When processing WGSL source, only the `TooLarge` error should be
131+
/// produced by the `Layouter`, as the front-end should not produce IR
132+
/// that would result in the other errors.
124133
#[derive(Clone, Copy, Debug, PartialEq, thiserror::Error)]
125134
pub enum LayoutErrorInner {
126135
#[error("Array element type {0:?} doesn't exist")]
@@ -129,6 +138,8 @@ pub enum LayoutErrorInner {
129138
InvalidStructMemberType(u32, Handle<crate::Type>),
130139
#[error("Type width must be a power of two")]
131140
NonPowerOfTwoWidth,
141+
#[error("Size exceeds limit of {MAX_TYPE_SIZE} bytes")]
142+
TooLarge,
132143
}
133144

134145
#[derive(Clone, Copy, Debug, PartialEq, thiserror::Error)]
@@ -168,7 +179,10 @@ impl Layouter {
168179
use crate::TypeInner as Ti;
169180

170181
for (ty_handle, ty) in gctx.types.iter().skip(self.layouts.len()) {
171-
let size = ty.inner.size(gctx);
182+
let size = ty
183+
.inner
184+
.try_size(gctx)
185+
.ok_or_else(|| LayoutErrorInner::TooLarge.with(ty_handle))?;
172186
let layout = match ty.inner {
173187
Ti::Scalar(scalar) | Ti::Atomic(scalar) => {
174188
let alignment = Alignment::new(scalar.width as u32)

naga/src/proc/type_methods.rs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//! [`Scalar`]: crate::Scalar
55
//! [`ScalarKind`]: crate::ScalarKind
66
7-
use crate::ir;
7+
use crate::{ir, valid::MAX_TYPE_SIZE};
88

99
use super::TypeResolution;
1010

@@ -190,18 +190,19 @@ impl crate::TypeInner {
190190
}
191191
}
192192

193-
/// Get the size of this type.
194-
pub fn size(&self, gctx: super::GlobalCtx) -> u32 {
193+
/// Attempt to calculate the size of this type. Returns `None` if the size
194+
/// exceeds the limit of [`crate::valid::MAX_TYPE_SIZE`].
195+
pub fn try_size(&self, gctx: super::GlobalCtx) -> Option<u32> {
195196
match *self {
196-
Self::Scalar(scalar) | Self::Atomic(scalar) => scalar.width as u32,
197-
Self::Vector { size, scalar } => size as u32 * scalar.width as u32,
197+
Self::Scalar(scalar) | Self::Atomic(scalar) => Some(scalar.width as u32),
198+
Self::Vector { size, scalar } => Some(size as u32 * scalar.width as u32),
198199
// matrices are treated as arrays of aligned columns
199200
Self::Matrix {
200201
columns,
201202
rows,
202203
scalar,
203-
} => super::Alignment::from(rows) * scalar.width as u32 * columns as u32,
204-
Self::Pointer { .. } | Self::ValuePointer { .. } => POINTER_SPAN,
204+
} => Some(super::Alignment::from(rows) * scalar.width as u32 * columns as u32),
205+
Self::Pointer { .. } | Self::ValuePointer { .. } => Some(POINTER_SPAN),
205206
Self::Array {
206207
base: _,
207208
size,
@@ -215,17 +216,35 @@ impl crate::TypeInner {
215216
// A dynamically-sized array has to have at least one element
216217
Ok(crate::proc::IndexableLength::Dynamic) => 1,
217218
};
218-
count * stride
219+
if count > MAX_TYPE_SIZE {
220+
// It shouldn't be possible to have an array of a zero-sized type, but
221+
// let's check just in case.
222+
None
223+
} else {
224+
count
225+
.checked_mul(stride)
226+
.filter(|size| *size <= MAX_TYPE_SIZE)
227+
}
219228
}
220-
Self::Struct { span, .. } => span,
229+
Self::Struct { span, .. } => Some(span),
221230
Self::Image { .. }
222231
| Self::Sampler { .. }
223232
| Self::AccelerationStructure { .. }
224233
| Self::RayQuery { .. }
225-
| Self::BindingArray { .. } => 0,
234+
| Self::BindingArray { .. } => Some(0),
226235
}
227236
}
228237

238+
/// Get the size of this type.
239+
///
240+
/// Panics if the size exceeds the limit of [`crate::valid::MAX_TYPE_SIZE`].
241+
/// Validated modules should not contain such types. Code working with
242+
/// modules prior to validation should use [`Self::try_size`] and handle the
243+
/// error appropriately.
244+
pub fn size(&self, gctx: super::GlobalCtx) -> u32 {
245+
self.try_size(gctx).expect("type is too large")
246+
}
247+
229248
/// Return the canonical form of `self`, or `None` if it's already in
230249
/// canonical form.
231250
///

naga/src/valid/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ pub use r#type::{Disalignment, PushConstantError, TypeError, TypeFlags, WidthErr
3535

3636
use self::handles::InvalidHandleError;
3737

38+
/// Maximum size of a type, in bytes.
39+
pub const MAX_TYPE_SIZE: u32 = 0x4000_0000; // 1GB
40+
3841
bitflags::bitflags! {
3942
/// Validation flags.
4043
///

0 commit comments

Comments
 (0)