Skip to content

Commit ef428fc

Browse files
andyleisersonjimblandy
authored andcommitted
Validate binding ranges against buffer size
1 parent 3d0fe3a commit ef428fc

File tree

22 files changed

+363
-213
lines changed

22 files changed

+363
-213
lines changed

wgpu-core/src/binding_model.rs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,48 @@ impl WebGpuError for CreateBindGroupLayoutError {
9494
}
9595
}
9696

97-
//TODO: refactor this to move out `enum BindingError`.
97+
#[derive(Clone, Debug, Error)]
98+
#[non_exhaustive]
99+
pub enum BindingError {
100+
#[error(transparent)]
101+
DestroyedResource(#[from] DestroyedResourceError),
102+
#[error("Buffer {buffer}: Binding with size {binding_size} at offset {offset} would overflow buffer size of {buffer_size}")]
103+
BindingRangeTooLarge {
104+
buffer: ResourceErrorIdent,
105+
offset: wgt::BufferAddress,
106+
binding_size: u64,
107+
buffer_size: u64,
108+
},
109+
#[error("Buffer {buffer}: Binding offset {offset} is greater than or equal to buffer size {buffer_size}")]
110+
BindingOffsetTooLarge {
111+
buffer: ResourceErrorIdent,
112+
offset: wgt::BufferAddress,
113+
buffer_size: u64,
114+
},
115+
}
98116

117+
impl WebGpuError for BindingError {
118+
fn webgpu_error_type(&self) -> ErrorType {
119+
match self {
120+
Self::DestroyedResource(e) => e.webgpu_error_type(),
121+
Self::BindingRangeTooLarge { .. } | Self::BindingOffsetTooLarge { .. } => {
122+
ErrorType::Validation
123+
}
124+
}
125+
}
126+
}
127+
128+
// TODO: there may be additional variants here that can be extracted into
129+
// `BindingError`.
99130
#[derive(Clone, Debug, Error)]
100131
#[non_exhaustive]
101132
pub enum CreateBindGroupError {
102133
#[error(transparent)]
103134
Device(#[from] DeviceError),
104135
#[error(transparent)]
105136
DestroyedResource(#[from] DestroyedResourceError),
137+
#[error(transparent)]
138+
BindingError(#[from] BindingError),
106139
#[error(
107140
"Binding count declared with at most {expected} items, but {actual} items were provided"
108141
)]
@@ -113,12 +146,6 @@ pub enum CreateBindGroupError {
113146
BindingArrayLengthMismatch { actual: usize, expected: usize },
114147
#[error("Array binding provided zero elements")]
115148
BindingArrayZeroLength,
116-
#[error("The bound range {range:?} of {buffer} overflows its size ({size})")]
117-
BindingRangeTooLarge {
118-
buffer: ResourceErrorIdent,
119-
range: Range<wgt::BufferAddress>,
120-
size: u64,
121-
},
122149
#[error("Binding size {actual} of {buffer} is less than minimum {min}")]
123150
BindingSizeTooSmall {
124151
buffer: ResourceErrorIdent,
@@ -233,14 +260,14 @@ impl WebGpuError for CreateBindGroupError {
233260
let e: &dyn WebGpuError = match self {
234261
Self::Device(e) => e,
235262
Self::DestroyedResource(e) => e,
263+
Self::BindingError(e) => e,
236264
Self::MissingBufferUsage(e) => e,
237265
Self::MissingTextureUsage(e) => e,
238266
Self::ResourceUsageCompatibility(e) => e,
239267
Self::InvalidResource(e) => e,
240268
Self::BindingArrayPartialLengthMismatch { .. }
241269
| Self::BindingArrayLengthMismatch { .. }
242270
| Self::BindingArrayZeroLength
243-
| Self::BindingRangeTooLarge { .. }
244271
| Self::BindingSizeTooSmall { .. }
245272
| Self::BindingsNumMismatch { .. }
246273
| Self::BindingZeroSize(_)

wgpu-core/src/command/bundle.rs

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,7 @@ fn set_pipeline(
602602
Ok(())
603603
}
604604

605+
// This function is duplicative of `render::set_index_buffer`.
605606
fn set_index_buffer(
606607
state: &mut State,
607608
buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
@@ -620,21 +621,20 @@ fn set_index_buffer(
620621
buffer.same_device(&state.device)?;
621622
buffer.check_usage(wgt::BufferUsages::INDEX)?;
622623

623-
let end = match size {
624-
Some(s) => offset + s.get(),
625-
None => buffer.size,
626-
};
624+
let end = buffer.resolve_binding_size(offset, size)?;
625+
627626
state
628627
.buffer_memory_init_actions
629628
.extend(buffer.initialization_status.read().create_action(
630629
&buffer,
631-
offset..end,
630+
offset..end.get(),
632631
MemoryInitKind::NeedsInitializedMemory,
633632
));
634-
state.set_index_buffer(buffer, index_format, offset..end);
633+
state.set_index_buffer(buffer, index_format, offset..end.get());
635634
Ok(())
636635
}
637636

637+
// This function is duplicative of `render::set_vertex_buffer`.
638638
fn set_vertex_buffer(
639639
state: &mut State,
640640
buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
@@ -662,18 +662,16 @@ fn set_vertex_buffer(
662662
buffer.same_device(&state.device)?;
663663
buffer.check_usage(wgt::BufferUsages::VERTEX)?;
664664

665-
let end = match size {
666-
Some(s) => offset + s.get(),
667-
None => buffer.size,
668-
};
665+
let end = buffer.resolve_binding_size(offset, size)?;
666+
669667
state
670668
.buffer_memory_init_actions
671669
.extend(buffer.initialization_status.read().create_action(
672670
&buffer,
673-
offset..end,
671+
offset..end.get(),
674672
MemoryInitKind::NeedsInitializedMemory,
675673
));
676-
state.vertex[slot as usize] = Some(VertexState::new(buffer, offset..end));
674+
state.vertex[slot as usize] = Some(VertexState::new(buffer, offset..end.get()));
677675
Ok(())
678676
}
679677

@@ -965,10 +963,14 @@ impl RenderBundle {
965963
size,
966964
} => {
967965
let buffer = buffer.try_raw(snatch_guard)?;
968-
let bb = hal::BufferBinding {
969-
buffer,
970-
offset: *offset,
971-
size: *size,
966+
let bb = unsafe {
967+
// SAFETY: The binding size was checked against the buffer size
968+
// in `set_index_buffer` and again in `IndexState::flush`.
969+
hal::BufferBinding::new_unchecked(
970+
buffer,
971+
*offset,
972+
size.expect("size was resolved in `RenderBundleEncoder::finish`"),
973+
)
972974
};
973975
unsafe { raw.set_index_buffer(bb, *index_format) };
974976
}
@@ -979,10 +981,14 @@ impl RenderBundle {
979981
size,
980982
} => {
981983
let buffer = buffer.try_raw(snatch_guard)?;
982-
let bb = hal::BufferBinding {
983-
buffer,
984-
offset: *offset,
985-
size: *size,
984+
let bb = unsafe {
985+
// SAFETY: The binding size was checked against the buffer size
986+
// in `set_vertex_buffer` and again in `VertexState::flush`.
987+
hal::BufferBinding::new_unchecked(
988+
buffer,
989+
*offset,
990+
size.expect("size was resolved in `RenderBundleEncoder::finish`"),
991+
)
986992
};
987993
unsafe { raw.set_vertex_buffer(*slot, bb) };
988994
}
@@ -1131,6 +1137,9 @@ crate::impl_trackable!(RenderBundle);
11311137
/// [`RenderBundleEncoder::finish`] records the currently set index buffer here,
11321138
/// and calls [`State::flush_index`] before any indexed draw command to produce
11331139
/// a `SetIndexBuffer` command if one is necessary.
1140+
///
1141+
/// Binding ranges must be validated against the size of the buffer before
1142+
/// being stored in `IndexState`.
11341143
#[derive(Debug)]
11351144
struct IndexState {
11361145
buffer: Arc<Buffer>,
@@ -1152,13 +1161,24 @@ impl IndexState {
11521161
/// Generate a `SetIndexBuffer` command to prepare for an indexed draw
11531162
/// command, if needed.
11541163
fn flush(&mut self) -> Option<ArcRenderCommand> {
1164+
// This was all checked before, but let's check again just in case.
1165+
let binding_size = self
1166+
.range
1167+
.end
1168+
.checked_sub(self.range.start)
1169+
.and_then(wgt::BufferSize::new);
1170+
assert!(
1171+
self.range.end <= self.buffer.size && binding_size.is_some(),
1172+
"index buffer range must have non-zero size and be contained in buffer",
1173+
);
1174+
11551175
if self.is_dirty {
11561176
self.is_dirty = false;
11571177
Some(ArcRenderCommand::SetIndexBuffer {
11581178
buffer: self.buffer.clone(),
11591179
index_format: self.format,
11601180
offset: self.range.start,
1161-
size: wgt::BufferSize::new(self.range.end - self.range.start),
1181+
size: binding_size,
11621182
})
11631183
} else {
11641184
None
@@ -1174,6 +1194,9 @@ impl IndexState {
11741194
/// calls this type's [`flush`] method just before any draw command to
11751195
/// produce a `SetVertexBuffer` commands if one is necessary.
11761196
///
1197+
/// Binding ranges must be validated against the size of the buffer before
1198+
/// being stored in `VertexState`.
1199+
///
11771200
/// [`flush`]: IndexState::flush
11781201
#[derive(Debug)]
11791202
struct VertexState {
@@ -1183,6 +1206,9 @@ struct VertexState {
11831206
}
11841207

11851208
impl VertexState {
1209+
/// Create a new `VertexState`.
1210+
///
1211+
/// The `range` must be contained within `buffer`.
11861212
fn new(buffer: Arc<Buffer>, range: Range<wgt::BufferAddress>) -> Self {
11871213
Self {
11881214
buffer,
@@ -1195,13 +1221,24 @@ impl VertexState {
11951221
///
11961222
/// `slot` is the index of the vertex buffer slot that `self` tracks.
11971223
fn flush(&mut self, slot: u32) -> Option<ArcRenderCommand> {
1224+
// This was all checked before, but let's check again just in case.
1225+
let binding_size = self
1226+
.range
1227+
.end
1228+
.checked_sub(self.range.start)
1229+
.and_then(wgt::BufferSize::new);
1230+
assert!(
1231+
self.range.end <= self.buffer.size && binding_size.is_some(),
1232+
"vertex buffer range must have non-zero size and be contained in buffer",
1233+
);
1234+
11981235
if self.is_dirty {
11991236
self.is_dirty = false;
12001237
Some(ArcRenderCommand::SetVertexBuffer {
12011238
slot,
12021239
buffer: self.buffer.clone(),
12031240
offset: self.range.start,
1204-
size: wgt::BufferSize::new(self.range.end - self.range.start),
1241+
size: binding_size,
12051242
})
12061243
} else {
12071244
None

wgpu-core/src/command/draw.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use wgt::error::{ErrorType, WebGpuError};
77
use super::bind::BinderError;
88
use crate::command::pass;
99
use crate::{
10-
binding_model::{LateMinBufferBindingSizeMismatch, PushConstantUploadError},
10+
binding_model::{BindingError, LateMinBufferBindingSizeMismatch, PushConstantUploadError},
1111
resource::{
1212
DestroyedResourceError, MissingBufferUsageError, MissingTextureUsageError,
1313
ResourceErrorIdent,
@@ -89,6 +89,8 @@ pub enum RenderCommandError {
8989
MissingTextureUsage(#[from] MissingTextureUsageError),
9090
#[error(transparent)]
9191
PushConstants(#[from] PushConstantUploadError),
92+
#[error(transparent)]
93+
BindingError(#[from] BindingError),
9294
#[error("Viewport size {{ w: {w}, h: {h} }} greater than device's requested `max_texture_dimension_2d` limit {max}, or less than zero")]
9395
InvalidViewportRectSize { w: f32, h: f32, max: u32 },
9496
#[error("Viewport has invalid rect {rect:?} for device's requested `max_texture_dimension_2d` limit; Origin less than -2 * `max_texture_dimension_2d` ({min}), or rect extends past 2 * `max_texture_dimension_2d` - 1 ({max})")]
@@ -110,6 +112,7 @@ impl WebGpuError for RenderCommandError {
110112
Self::MissingBufferUsage(e) => e,
111113
Self::MissingTextureUsage(e) => e,
112114
Self::PushConstants(e) => e,
115+
Self::BindingError(e) => e,
113116

114117
Self::BindGroupIndexOutOfRange { .. }
115118
| Self::VertexBufferIndexOutOfRange { .. }

wgpu-core/src/command/render.rs

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use alloc::{borrow::Cow, sync::Arc, vec::Vec};
2-
use core::{fmt, num::NonZeroU32, ops::Range, str};
2+
use core::{fmt, num::NonZeroU32, str};
33

44
use arrayvec::ArrayVec;
55
use thiserror::Error;
@@ -356,13 +356,17 @@ struct IndexState {
356356
}
357357

358358
impl IndexState {
359-
fn update_buffer(&mut self, range: Range<BufferAddress>, format: IndexFormat) {
359+
fn update_buffer<B: hal::DynBuffer + ?Sized>(
360+
&mut self,
361+
binding: &hal::BufferBinding<'_, B>,
362+
format: IndexFormat,
363+
) {
360364
self.buffer_format = Some(format);
361365
let shift = match format {
362366
IndexFormat::Uint16 => 1,
363367
IndexFormat::Uint32 => 2,
364368
};
365-
self.limit = (range.end - range.start) >> shift;
369+
self.limit = binding.size.get() >> shift;
366370
}
367371

368372
fn reset(&mut self) {
@@ -2322,6 +2326,7 @@ fn set_pipeline(
23222326
Ok(())
23232327
}
23242328

2329+
// This function is duplicative of `bundle::set_index_buffer`.
23252330
fn set_index_buffer(
23262331
state: &mut State,
23272332
cmd_buf: &Arc<CommandBuffer>,
@@ -2341,33 +2346,27 @@ fn set_index_buffer(
23412346
buffer.same_device_as(cmd_buf.as_ref())?;
23422347

23432348
buffer.check_usage(BufferUsages::INDEX)?;
2344-
let buf_raw = buffer.try_raw(state.general.snatch_guard)?;
23452349

2346-
let end = match size {
2347-
Some(s) => offset + s.get(),
2348-
None => buffer.size,
2349-
};
2350-
state.index.update_buffer(offset..end, index_format);
2350+
let binding = buffer
2351+
.binding(offset, size, state.general.snatch_guard)
2352+
.map_err(RenderCommandError::from)?;
2353+
state.index.update_buffer(&binding, index_format);
23512354

23522355
state.general.buffer_memory_init_actions.extend(
23532356
buffer.initialization_status.read().create_action(
23542357
&buffer,
2355-
offset..end,
2358+
offset..(offset + binding.size.get()),
23562359
MemoryInitKind::NeedsInitializedMemory,
23572360
),
23582361
);
23592362

2360-
let bb = hal::BufferBinding {
2361-
buffer: buf_raw,
2362-
offset,
2363-
size,
2364-
};
23652363
unsafe {
2366-
hal::DynCommandEncoder::set_index_buffer(state.general.raw_encoder, bb, index_format);
2364+
hal::DynCommandEncoder::set_index_buffer(state.general.raw_encoder, binding, index_format);
23672365
}
23682366
Ok(())
23692367
}
23702368

2369+
// This function is duplicative of `render::set_vertex_buffer`.
23712370
fn set_vertex_buffer(
23722371
state: &mut State,
23732372
cmd_buf: &Arc<CommandBuffer>,
@@ -2399,30 +2398,22 @@ fn set_vertex_buffer(
23992398
}
24002399

24012400
buffer.check_usage(BufferUsages::VERTEX)?;
2402-
let buf_raw = buffer.try_raw(state.general.snatch_guard)?;
24032401

2404-
//TODO: where are we checking that the offset is in bound?
2405-
let buffer_size = match size {
2406-
Some(s) => s.get(),
2407-
None => buffer.size - offset,
2408-
};
2409-
state.vertex.buffer_sizes[slot as usize] = Some(buffer_size);
2402+
let binding = buffer
2403+
.binding(offset, size, state.general.snatch_guard)
2404+
.map_err(RenderCommandError::from)?;
2405+
state.vertex.buffer_sizes[slot as usize] = Some(binding.size.get());
24102406

24112407
state.general.buffer_memory_init_actions.extend(
24122408
buffer.initialization_status.read().create_action(
24132409
&buffer,
2414-
offset..(offset + buffer_size),
2410+
offset..(offset + binding.size.get()),
24152411
MemoryInitKind::NeedsInitializedMemory,
24162412
),
24172413
);
24182414

2419-
let bb = hal::BufferBinding {
2420-
buffer: buf_raw,
2421-
offset,
2422-
size,
2423-
};
24242415
unsafe {
2425-
hal::DynCommandEncoder::set_vertex_buffer(state.general.raw_encoder, slot, bb);
2416+
hal::DynCommandEncoder::set_vertex_buffer(state.general.raw_encoder, slot, binding);
24262417
}
24272418
if let Some(pipeline) = state.pipeline.as_ref() {
24282419
state.vertex.update_limits(&pipeline.vertex_steps);

0 commit comments

Comments
 (0)