Skip to content

Commit d282ee3

Browse files
fix(core): validate start + size for buffer ops in more places
1 parent 9e091e8 commit d282ee3

6 files changed

Lines changed: 118 additions & 30 deletions

File tree

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: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ 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
)]
@@ -185,6 +193,7 @@ impl WebGpuError for TransferError {
185193

186194
Self::BufferOverrun { .. }
187195
| Self::TextureOverrun { .. }
196+
| Self::BufferStartOffsetOverrun { .. }
188197
| Self::UnsupportedPartialTransfer { .. }
189198
| Self::InvalidCopyWithinSameTexture { .. }
190199
| Self::InvalidTextureAspect { .. }
@@ -342,8 +351,15 @@ 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 {
354+
if offset > buffer_size {
355+
return Err(TransferError::BufferStartOffsetOverrun {
356+
start_offset: offset,
357+
buffer_size: bytes_in_copy,
358+
side: buffer_side,
359+
});
360+
}
361+
// NOTE: Should never underflow because of our earlier check.
362+
if bytes_in_copy > buffer_size - offset {
347363
return Err(TransferError::BufferOverrun {
348364
start_offset: offset,
349365
size: bytes_in_copy,
@@ -983,10 +999,19 @@ 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+
// TODO: This check isn't part of the spec., but it looks like it should be.
1003+
if source_offset > src_buffer.size {
1004+
return Err(TransferError::BufferStartOffsetOverrun {
1005+
start_offset: destination_offset,
1006+
buffer_size: dst_buffer.size,
1007+
side: CopySide::Destination,
1008+
}
1009+
.into());
1010+
}
1011+
let size = size.unwrap_or_else(|| {
1012+
// NOTE: Should never underflow because of our earlier check.
1013+
src_buffer.size - source_offset
1014+
});
9901015

9911016
if !size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
9921017
return Err(TransferError::UnalignedCopySize(size).into());
@@ -1019,8 +1044,7 @@ pub(super) fn copy_buffer_to_buffer(
10191044
}
10201045
}
10211046

1022-
let destination_end_offset = destination_offset + size;
1023-
if source_end_offset > src_buffer.size {
1047+
if size > src_buffer.size - source_offset {
10241048
return Err(TransferError::BufferOverrun {
10251049
start_offset: source_offset,
10261050
size,
@@ -1029,7 +1053,21 @@ pub(super) fn copy_buffer_to_buffer(
10291053
}
10301054
.into());
10311055
}
1032-
if destination_end_offset > dst_buffer.size {
1056+
// NOTE: Should never overflow because of our earlier check.
1057+
let source_end_offset = source_offset + size;
1058+
1059+
// TODO: Checks for source buffer overrun don't quite match spec., but it seems important to do
1060+
// things in this order.
1061+
if destination_offset > dst_buffer.size {
1062+
return Err(TransferError::BufferStartOffsetOverrun {
1063+
start_offset: destination_offset,
1064+
buffer_size: dst_buffer.size,
1065+
side: CopySide::Destination,
1066+
}
1067+
.into());
1068+
}
1069+
// NOTE: Should never underflow because of our earlier check.
1070+
if size > dst_buffer.size - destination_offset {
10331071
return Err(TransferError::BufferOverrun {
10341072
start_offset: destination_offset,
10351073
size,
@@ -1038,6 +1076,8 @@ pub(super) fn copy_buffer_to_buffer(
10381076
}
10391077
.into());
10401078
}
1079+
// NOTE: Should never overflow because of our earlier check.
1080+
let destination_end_offset = destination_offset + size;
10411081

10421082
// This must happen after parameter validation (so that errors are reported
10431083
// as required by the spec), but before any side effects.
@@ -1051,14 +1091,14 @@ pub(super) fn copy_buffer_to_buffer(
10511091
.buffer_memory_init_actions
10521092
.extend(dst_buffer.initialization_status.read().create_action(
10531093
dst_buffer,
1054-
destination_offset..(destination_offset + size),
1094+
destination_offset..destination_end_offset,
10551095
MemoryInitKind::ImplicitlyInitialized,
10561096
));
10571097
state
10581098
.buffer_memory_init_actions
10591099
.extend(src_buffer.initialization_status.read().create_action(
10601100
src_buffer,
1061-
source_offset..(source_offset + size),
1101+
source_offset..source_end_offset,
10621102
MemoryInitKind::NeedsInitializedMemory,
10631103
));
10641104

wgpu-core/src/device/queue.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,15 @@ 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 {
663+
664+
if buffer_offset > buffer.size {
665+
return Err(TransferError::BufferStartOffsetOverrun {
666+
start_offset: buffer_offset,
667+
buffer_size: buffer_size.get(),
668+
side: CopySide::Destination,
669+
});
670+
}
671+
if buffer_size.get() > buffer.size - buffer_offset {
664672
return Err(TransferError::BufferOverrun {
665673
start_offset: buffer_offset,
666674
size: buffer_size.get(),

wgpu-core/src/resource.rs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,22 @@ pub enum BufferAccessError {
301301
MapAborted,
302302
#[error(transparent)]
303303
InvalidResource(#[from] InvalidResourceError),
304+
#[error("Map start offset ({offset}) is out-of-bounds for buffer of size {buffer_size}")]
305+
MapStartOffsetOverrun {
306+
offset: wgt::BufferAddress,
307+
buffer_size: wgt::BufferAddress,
308+
},
309+
#[error(
310+
"Map end offset (start at {} + size of {}) is out-of-bounds for buffer of size {}",
311+
offset,
312+
size,
313+
buffer_size
314+
)]
315+
MapEndOffsetOverrun {
316+
offset: wgt::BufferAddress,
317+
size: wgt::BufferAddress,
318+
buffer_size: wgt::BufferAddress,
319+
},
304320
}
305321

306322
impl WebGpuError for BufferAccessError {
@@ -321,7 +337,9 @@ impl WebGpuError for BufferAccessError {
321337
| Self::OutOfBoundsUnderrun { .. }
322338
| Self::OutOfBoundsOverrun { .. }
323339
| Self::NegativeRange { .. }
324-
| Self::MapAborted => return ErrorType::Validation,
340+
| Self::MapAborted
341+
| Self::MapStartOffsetOverrun { .. }
342+
| Self::MapEndOffsetOverrun { .. } => return ErrorType::Validation,
325343
};
326344
e.webgpu_error_type()
327345
}
@@ -592,6 +610,27 @@ impl Buffer {
592610
return Err((op, BufferAccessError::UnalignedRangeSize { range_size }));
593611
}
594612

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

597636
if !range.start.is_multiple_of(wgt::MAP_ALIGNMENT)
@@ -704,11 +743,18 @@ impl Buffer {
704743
let map_state = &*self.map_state.lock();
705744
match *map_state {
706745
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,
746+
if offset > self.size {
747+
return Err(BufferAccessError::MapStartOffsetOverrun {
748+
offset,
749+
buffer_size: self.size,
750+
});
751+
}
752+
// NOTE: Should never underflow because of our earlier check.
753+
if range_size > self.size - offset {
754+
return Err(BufferAccessError::MapEndOffsetOverrun {
755+
offset,
756+
size: range_size,
757+
buffer_size: self.size,
712758
});
713759
}
714760
let ptr = unsafe { staging_buffer.ptr() };

0 commit comments

Comments
 (0)