Skip to content

Commit 32f254a

Browse files
fix(core)!: validate start + size for buffer ops in more places
Co-authored-by: Jim Blandy <[email protected]>
1 parent e7a9a23 commit 32f254a

7 files changed

Lines changed: 154 additions & 74 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,11 @@ By @cwfitzgerald in [#8999](https://github.com/gfx-rs/wgpu/pull/8999).
9191

9292
#### General
9393

94-
- Replace `TransferError::BufferOverrun::end_offset` with a `size` field. By @ErichDonGubler in [9073](https://github.com/gfx-rs/wgpu/pull/9073).
94+
- Several error APIs were changed by @ErichDonGubler in [#9073](https://github.com/gfx-rs/wgpu/pull/9073):
95+
- `BufferAccessError`:
96+
- Split the `OutOfBoundsOverrun` variant into new `OutOfBoundsStartOffsetOverrun` and `OutOfBoundsEndOffsetOverrun` variants.
97+
- Removed the `NegativeRange` variant in favor of new `MapStartOffsetUnderrun` and `MapStartOffsetOverrun` variants.
98+
- Split the `TransferError::BufferOverrun` variant into new `BufferStartOffsetOverrun` and `BufferEndOffsetOverrun` variants.
9599

96100
#### Metal
97101

player/tests/player/data/buffer-copy.ron

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@
2121
WriteBuffer(
2222
id: PointerId(0x10),
2323
data: File("data1.bin"),
24-
range: (
25-
start: 0,
26-
end: 16,
27-
),
24+
offset: 0,
25+
size: 16,
2826
queued: true,
2927
),
3028
Submit(1, []),

player/tests/player/data/clear-buffer-texture.ron

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,8 @@
7474
WriteBuffer(
7575
id: PointerId(0x110),
7676
data: File("data1.bin"),
77-
range: (
78-
start: 0,
79-
end: 16,
80-
),
77+
offset: 0,
78+
size: 16,
8179
queued: true,
8280
),
8381
Submit(1, [

player/tests/player/data/zero-init-buffer.ron

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,8 @@
6868
WriteBuffer(
6969
id: PointerId(0x120),
7070
data: File("data1.bin"),
71-
range: (
72-
start: 4,
73-
end: 20,
74-
),
71+
offset: 4,
72+
size: 20,
7573
queued: true,
7674
),
7775
CreateShaderModule(

wgpu-core/src/command/transfer.rs

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,18 @@ pub enum TransferError {
4747
MissingBufferUsage(#[from] MissingBufferUsageError),
4848
#[error(transparent)]
4949
MissingTextureUsage(#[from] MissingTextureUsageError),
50+
#[error(
51+
"Copy at offset {start_offset} bytes would end up overrunning the bounds of the {side:?} buffer of size {buffer_size}"
52+
)]
53+
BufferStartOffsetOverrun {
54+
start_offset: BufferAddress,
55+
buffer_size: BufferAddress,
56+
side: CopySide,
57+
},
5058
#[error(
5159
"Copy at offset {start_offset} for {size} bytes would end up overrunning the bounds of the {side:?} buffer of size {buffer_size}"
5260
)]
53-
BufferOverrun {
61+
BufferEndOffsetOverrun {
5462
start_offset: BufferAddress,
5563
size: BufferAddress,
5664
buffer_size: BufferAddress,
@@ -183,8 +191,9 @@ impl WebGpuError for TransferError {
183191
Self::MissingTextureUsage(e) => e,
184192
Self::MemoryInitFailure(e) => e,
185193

186-
Self::BufferOverrun { .. }
194+
Self::BufferEndOffsetOverrun { .. }
187195
| Self::TextureOverrun { .. }
196+
| Self::BufferStartOffsetOverrun { .. }
188197
| Self::UnsupportedPartialTransfer { .. }
189198
| Self::InvalidCopyWithinSameTexture { .. }
190199
| Self::InvalidTextureAspect { .. }
@@ -342,9 +351,16 @@ pub(crate) fn validate_linear_texture_data(
342351
return Err(TransferError::UnspecifiedRowsPerImage);
343352
};
344353

345-
// Avoid underflow in the subtraction by checking bytes_in_copy against buffer_size first.
346-
if bytes_in_copy > buffer_size || offset > buffer_size - bytes_in_copy {
347-
return Err(TransferError::BufferOverrun {
354+
if offset > buffer_size {
355+
return Err(TransferError::BufferStartOffsetOverrun {
356+
start_offset: offset,
357+
buffer_size,
358+
side: buffer_side,
359+
});
360+
}
361+
// NOTE: Should never underflow because of our earlier check.
362+
if bytes_in_copy > buffer_size - offset {
363+
return Err(TransferError::BufferEndOffsetOverrun {
348364
start_offset: offset,
349365
size: bytes_in_copy,
350366
buffer_size,
@@ -983,10 +999,18 @@ pub(super) fn copy_buffer_to_buffer(
983999
.map_err(TransferError::MissingBufferUsage)?;
9841000
let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer, state.snatch_guard));
9851001

986-
let (size, source_end_offset) = match size {
987-
Some(size) => (size, source_offset + size),
988-
None => (src_buffer.size - source_offset, src_buffer.size),
989-
};
1002+
if source_offset > src_buffer.size {
1003+
return Err(TransferError::BufferStartOffsetOverrun {
1004+
start_offset: source_offset,
1005+
buffer_size: src_buffer.size,
1006+
side: CopySide::Source,
1007+
}
1008+
.into());
1009+
}
1010+
let size = size.unwrap_or_else(|| {
1011+
// NOTE: Should never underflow because of our earlier check.
1012+
src_buffer.size - source_offset
1013+
});
9901014

9911015
if !size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
9921016
return Err(TransferError::UnalignedCopySize(size).into());
@@ -1019,25 +1043,38 @@ pub(super) fn copy_buffer_to_buffer(
10191043
}
10201044
}
10211045

1022-
let destination_end_offset = destination_offset + size;
1023-
if source_end_offset > src_buffer.size {
1024-
return Err(TransferError::BufferOverrun {
1046+
if size > src_buffer.size - source_offset {
1047+
return Err(TransferError::BufferEndOffsetOverrun {
10251048
start_offset: source_offset,
10261049
size,
10271050
buffer_size: src_buffer.size,
10281051
side: CopySide::Source,
10291052
}
10301053
.into());
10311054
}
1032-
if destination_end_offset > dst_buffer.size {
1033-
return Err(TransferError::BufferOverrun {
1055+
// NOTE: Should never overflow because of our earlier check.
1056+
let source_end_offset = source_offset + size;
1057+
1058+
if destination_offset > dst_buffer.size {
1059+
return Err(TransferError::BufferStartOffsetOverrun {
1060+
start_offset: destination_offset,
1061+
buffer_size: dst_buffer.size,
1062+
side: CopySide::Destination,
1063+
}
1064+
.into());
1065+
}
1066+
// NOTE: Should never underflow because of our earlier check.
1067+
if size > dst_buffer.size - destination_offset {
1068+
return Err(TransferError::BufferEndOffsetOverrun {
10341069
start_offset: destination_offset,
10351070
size,
10361071
buffer_size: dst_buffer.size,
10371072
side: CopySide::Destination,
10381073
}
10391074
.into());
10401075
}
1076+
// NOTE: Should never overflow because of our earlier check.
1077+
let destination_end_offset = destination_offset + size;
10411078

10421079
// This must happen after parameter validation (so that errors are reported
10431080
// as required by the spec), but before any side effects.
@@ -1051,14 +1088,14 @@ pub(super) fn copy_buffer_to_buffer(
10511088
.buffer_memory_init_actions
10521089
.extend(dst_buffer.initialization_status.read().create_action(
10531090
dst_buffer,
1054-
destination_offset..(destination_offset + size),
1091+
destination_offset..destination_end_offset,
10551092
MemoryInitKind::ImplicitlyInitialized,
10561093
));
10571094
state
10581095
.buffer_memory_init_actions
10591096
.extend(src_buffer.initialization_status.read().create_action(
10601097
src_buffer,
1061-
source_offset..(source_offset + size),
1098+
source_offset..source_end_offset,
10621099
MemoryInitKind::NeedsInitializedMemory,
10631100
));
10641101

wgpu-core/src/device/queue.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -660,8 +660,16 @@ impl Queue {
660660
if !buffer_offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
661661
return Err(TransferError::UnalignedBufferOffset(buffer_offset));
662662
}
663-
if buffer_offset + buffer_size.get() > buffer.size {
664-
return Err(TransferError::BufferOverrun {
663+
664+
if buffer_offset > buffer.size {
665+
return Err(TransferError::BufferStartOffsetOverrun {
666+
start_offset: buffer_offset,
667+
buffer_size: buffer.size,
668+
side: CopySide::Destination,
669+
});
670+
}
671+
if buffer_size.get() > buffer.size - buffer_offset {
672+
return Err(TransferError::BufferEndOffsetOverrun {
665673
start_offset: buffer_offset,
666674
size: buffer_size.get(),
667675
buffer_size: buffer.size,

wgpu-core/src/resource.rs

Lines changed: 80 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -281,26 +281,45 @@ pub enum BufferAccessError {
281281
#[error("Buffer range size invalid: range_size {range_size} must be multiple of 4")]
282282
UnalignedRangeSize { range_size: wgt::BufferAddress },
283283
#[error("Buffer access out of bounds: index {index} would underrun the buffer (limit: {min})")]
284-
OutOfBoundsUnderrun {
284+
OutOfBoundsStartOffsetUnderrun {
285285
index: wgt::BufferAddress,
286286
min: wgt::BufferAddress,
287287
},
288288
#[error(
289-
"Buffer access out of bounds: last index {index} would overrun the buffer (limit: {max})"
289+
"Buffer access out of bounds: start offset {index} would overrun the buffer (limit: {max})"
290290
)]
291-
OutOfBoundsOverrun {
291+
OutOfBoundsStartOffsetOverrun {
292292
index: wgt::BufferAddress,
293293
max: wgt::BufferAddress,
294294
},
295-
#[error("Buffer map range start {start} is greater than end {end}")]
296-
NegativeRange {
297-
start: wgt::BufferAddress,
298-
end: wgt::BufferAddress,
295+
#[error(
296+
"Buffer access out of bounds: start offset {index} + size {size} would overrun the buffer (limit: {max})"
297+
)]
298+
OutOfBoundsEndOffsetOverrun {
299+
index: wgt::BufferAddress,
300+
size: wgt::BufferAddress,
301+
max: wgt::BufferAddress,
299302
},
300303
#[error("Buffer map aborted")]
301304
MapAborted,
302305
#[error(transparent)]
303306
InvalidResource(#[from] InvalidResourceError),
307+
#[error("Map start offset ({offset}) is out-of-bounds for buffer of size {buffer_size}")]
308+
MapStartOffsetOverrun {
309+
offset: wgt::BufferAddress,
310+
buffer_size: wgt::BufferAddress,
311+
},
312+
#[error(
313+
"Map end offset (start at {} + size of {}) is out-of-bounds for buffer of size {}",
314+
offset,
315+
size,
316+
buffer_size
317+
)]
318+
MapEndOffsetOverrun {
319+
offset: wgt::BufferAddress,
320+
size: wgt::BufferAddress,
321+
buffer_size: wgt::BufferAddress,
322+
},
304323
}
305324

306325
impl WebGpuError for BufferAccessError {
@@ -318,10 +337,12 @@ impl WebGpuError for BufferAccessError {
318337
| Self::UnalignedRange
319338
| Self::UnalignedOffset { .. }
320339
| Self::UnalignedRangeSize { .. }
321-
| Self::OutOfBoundsUnderrun { .. }
322-
| Self::OutOfBoundsOverrun { .. }
323-
| Self::NegativeRange { .. }
324-
| Self::MapAborted => return ErrorType::Validation,
340+
| Self::OutOfBoundsStartOffsetUnderrun { .. }
341+
| Self::OutOfBoundsStartOffsetOverrun { .. }
342+
| Self::OutOfBoundsEndOffsetOverrun { .. }
343+
| Self::MapAborted
344+
| Self::MapStartOffsetOverrun { .. }
345+
| Self::MapEndOffsetOverrun { .. } => return ErrorType::Validation,
325346
};
326347
e.webgpu_error_type()
327348
}
@@ -592,10 +613,30 @@ impl Buffer {
592613
return Err((op, BufferAccessError::UnalignedRangeSize { range_size }));
593614
}
594615

595-
let range = offset..(offset + range_size);
616+
if offset > self.size {
617+
return Err((
618+
op,
619+
BufferAccessError::MapStartOffsetOverrun {
620+
offset,
621+
buffer_size: self.size,
622+
},
623+
));
624+
}
625+
// NOTE: Should never underflow because of our earlier check.
626+
if range_size > self.size - offset {
627+
return Err((
628+
op,
629+
BufferAccessError::MapEndOffsetOverrun {
630+
offset,
631+
size: range_size,
632+
buffer_size: self.size,
633+
},
634+
));
635+
}
636+
let end_offset = offset + range_size;
596637

597-
if !range.start.is_multiple_of(wgt::MAP_ALIGNMENT)
598-
|| !range.end.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT)
638+
if !offset.is_multiple_of(wgt::MAP_ALIGNMENT)
639+
|| !end_offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT)
599640
{
600641
return Err((op, BufferAccessError::UnalignedRange));
601642
}
@@ -609,25 +650,6 @@ impl Buffer {
609650
return Err((op, e.into()));
610651
}
611652

612-
if range.start > range.end {
613-
return Err((
614-
op,
615-
BufferAccessError::NegativeRange {
616-
start: range.start,
617-
end: range.end,
618-
},
619-
));
620-
}
621-
if range.end > self.size {
622-
return Err((
623-
op,
624-
BufferAccessError::OutOfBoundsOverrun {
625-
index: range.end,
626-
max: self.size,
627-
},
628-
));
629-
}
630-
631653
let device = &self.device;
632654
if let Err(e) = device.check_is_valid() {
633655
return Err((op, e.into()));
@@ -650,7 +672,8 @@ impl Buffer {
650672
return Err((op, BufferAccessError::MapAlreadyPending));
651673
}
652674
BufferMapState::Idle => BufferMapState::Waiting(BufferPendingMapping {
653-
range,
675+
// NOTE: Should never overflow because of our earlier check.
676+
range: offset..end_offset,
654677
op,
655678
_parent_buffer: self.clone(),
656679
}),
@@ -704,11 +727,18 @@ impl Buffer {
704727
let map_state = &*self.map_state.lock();
705728
match *map_state {
706729
BufferMapState::Init { ref staging_buffer } => {
707-
// offset (u64) can not be < 0, so no need to validate the lower bound
708-
if offset + range_size > self.size {
709-
return Err(BufferAccessError::OutOfBoundsOverrun {
710-
index: offset + range_size - 1,
711-
max: self.size,
730+
if offset > self.size {
731+
return Err(BufferAccessError::MapStartOffsetOverrun {
732+
offset,
733+
buffer_size: self.size,
734+
});
735+
}
736+
// NOTE: Should never underflow because of our earlier check.
737+
if range_size > self.size - offset {
738+
return Err(BufferAccessError::MapEndOffsetOverrun {
739+
offset,
740+
size: range_size,
741+
buffer_size: self.size,
712742
});
713743
}
714744
let ptr = unsafe { staging_buffer.ptr() };
@@ -720,15 +750,22 @@ impl Buffer {
720750
ref range,
721751
..
722752
} => {
753+
if offset > range.end {
754+
return Err(BufferAccessError::OutOfBoundsStartOffsetOverrun {
755+
index: offset,
756+
max: range.end,
757+
});
758+
}
723759
if offset < range.start {
724-
return Err(BufferAccessError::OutOfBoundsUnderrun {
760+
return Err(BufferAccessError::OutOfBoundsStartOffsetUnderrun {
725761
index: offset,
726762
min: range.start,
727763
});
728764
}
729-
if offset + range_size > range.end {
730-
return Err(BufferAccessError::OutOfBoundsOverrun {
731-
index: offset + range_size - 1,
765+
if range_size > range.end - offset {
766+
return Err(BufferAccessError::OutOfBoundsEndOffsetOverrun {
767+
index: offset,
768+
size: range_size,
732769
max: range.end,
733770
});
734771
}

0 commit comments

Comments
 (0)