Skip to content

Commit 96c4e09

Browse files
committed
copy_misaligned_words: avoid out-of-bounds accesses
1 parent 571ce5f commit 96c4e09

File tree

1 file changed

+109
-26
lines changed

1 file changed

+109
-26
lines changed

src/mem/impls.rs

Lines changed: 109 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,61 @@ unsafe fn read_usize_unaligned(x: *const usize) -> usize {
4141
core::mem::transmute(x_read)
4242
}
4343

44+
/// Load `load_sz` many bytes from `src`, which must be usize-aligned. Acts as if we did a `usize`
45+
/// read with the out-of-bounds part filled with 0s.
46+
/// `load_sz` be strictly less than `WORD_SIZE`.
47+
#[cfg(not(feature = "mem-unaligned"))]
48+
#[inline(always)]
49+
unsafe fn load_aligned_partial(src: *const usize, load_sz: usize) -> usize {
50+
let mut i = 0;
51+
let mut out = 0usize;
52+
macro_rules! load_prefix {
53+
($($ty:ty)+) => {$(
54+
let chunk_sz = core::mem::size_of::<$ty>();
55+
if (load_sz & chunk_sz) != 0 {
56+
// Since we are doing the large reads first, this must still be aligned to `chunk_sz`.
57+
*(&raw mut out).byte_add(i).cast::<$ty>() = *src.byte_add(i).cast::<$ty>();
58+
i |= chunk_sz;
59+
}
60+
)+};
61+
}
62+
// We can read up to 7 bytes here, which is enough for WORD_SIZE of 8
63+
// (since `load_size < WORD_SIZE`).
64+
const { assert!(WORD_SIZE <= 8) };
65+
load_prefix!(u32 u16 u8);
66+
debug_assert!(i == load_sz);
67+
out
68+
}
69+
70+
/// Load `load_sz` many bytes from `src.byte_add(WORD_SIZE - load_sz)`. `src` must be `usize`-aligned.
71+
/// The bytes are returned as the *last* bytes of the return value, i.e., acts as if we had done
72+
/// a `usize` read from `src`, with the out-of-bounds part filled with 0s.
73+
/// `load_sz` be strictly less than `WORD_SIZE`.
74+
#[cfg(not(feature = "mem-unaligned"))]
75+
#[inline(always)]
76+
unsafe fn load_aligned_end_partial(src: *const usize, load_sz: usize) -> usize {
77+
let mut i = 0;
78+
let mut out = 0usize;
79+
let start_shift = WORD_SIZE - load_sz;
80+
macro_rules! load_prefix {
81+
($($ty:ty)+) => {$(
82+
let chunk_sz = core::mem::size_of::<$ty>();
83+
if (load_sz & chunk_sz) != 0 {
84+
// Since we are doing the small reads first, `start_shift + i` has in the mean
85+
// time become aligned to `chunk_sz`.
86+
*(&raw mut out).byte_add(start_shift + i).cast::<$ty>() = *src.byte_add(start_shift + i).cast::<$ty>();
87+
i |= chunk_sz;
88+
}
89+
)+};
90+
}
91+
// We can read up to 7 bytes here, which is enough for WORD_SIZE of 8
92+
// (since `load_size < WORD_SIZE`).
93+
const { assert!(WORD_SIZE <= 8) };
94+
load_prefix!(u8 u16 u32);
95+
debug_assert!(i == load_sz);
96+
out
97+
}
98+
4499
#[inline(always)]
45100
pub unsafe fn copy_forward(mut dest: *mut u8, mut src: *const u8, mut n: usize) {
46101
#[inline(always)]
@@ -66,40 +121,54 @@ pub unsafe fn copy_forward(mut dest: *mut u8, mut src: *const u8, mut n: usize)
66121
}
67122
}
68123

124+
/// `n` is in units of bytes, but must be a multiple of the word size and must not be 0.
125+
/// `src` *must not* be `usize`-aligned.
69126
#[cfg(not(feature = "mem-unaligned"))]
70127
#[inline(always)]
71128
unsafe fn copy_forward_misaligned_words(dest: *mut u8, src: *const u8, n: usize) {
129+
debug_assert!(n > 0 && n % WORD_SIZE == 0);
130+
72131
let mut dest_usize = dest as *mut usize;
73132
let dest_end = dest.wrapping_add(n) as *mut usize;
74133

75134
// Calculate the misalignment offset and shift needed to reassemble value.
135+
// Since `src` is definitely not aligned, `offset` is in the range 1..WORD_SIZE.
76136
let offset = src as usize & WORD_MASK;
77137
let shift = offset * 8;
78138

79139
// Realign src
80-
let mut src_aligned = (src as usize & !WORD_MASK) as *mut usize;
81-
// This will read (but won't use) bytes out of bound.
82-
// cfg needed because not all targets will have atomic loads that can be lowered
83-
// (e.g. BPF, MSP430), or provided by an external library (e.g. RV32I)
84-
#[cfg(target_has_atomic_load_store = "ptr")]
85-
let mut prev_word = core::intrinsics::atomic_load_unordered(src_aligned);
86-
#[cfg(not(target_has_atomic_load_store = "ptr"))]
87-
let mut prev_word = core::ptr::read_volatile(src_aligned);
140+
let mut src_aligned = src.byte_sub(offset) as *mut usize;
141+
let mut prev_word = load_aligned_end_partial(src_aligned, WORD_SIZE - offset);
88142

89-
while dest_usize < dest_end {
143+
while dest_usize.wrapping_add(1) < dest_end {
90144
src_aligned = src_aligned.wrapping_add(1);
91145
let cur_word = *src_aligned;
92146
#[cfg(target_endian = "little")]
93-
let resembled = prev_word >> shift | cur_word << (WORD_SIZE * 8 - shift);
147+
let reassembled = prev_word >> shift | cur_word << (WORD_SIZE * 8 - shift);
94148
#[cfg(target_endian = "big")]
95-
let resembled = prev_word << shift | cur_word >> (WORD_SIZE * 8 - shift);
149+
let reassembled = prev_word << shift | cur_word >> (WORD_SIZE * 8 - shift);
96150
prev_word = cur_word;
97151

98-
*dest_usize = resembled;
152+
*dest_usize = reassembled;
99153
dest_usize = dest_usize.wrapping_add(1);
100154
}
155+
156+
// There's one more element left to go, and we can't use the loop for that as on the `src` side,
157+
// it is partially out-of-bounds.
158+
src_aligned = src_aligned.wrapping_add(1);
159+
let cur_word = load_aligned_partial(src_aligned, offset);
160+
#[cfg(target_endian = "little")]
161+
let reassembled = prev_word >> shift | cur_word << (WORD_SIZE * 8 - shift);
162+
#[cfg(target_endian = "big")]
163+
let reassembled = prev_word << shift | cur_word >> (WORD_SIZE * 8 - shift);
164+
// prev_word does not matter any more
165+
166+
*dest_usize = reassembled;
167+
// dest_usize does not matter any more
101168
}
102169

170+
/// `n` is in units of bytes, but must be a multiple of the word size and must not be 0.
171+
/// `src` *must not* be `usize`-aligned.
103172
#[cfg(feature = "mem-unaligned")]
104173
#[inline(always)]
105174
unsafe fn copy_forward_misaligned_words(dest: *mut u8, src: *const u8, n: usize) {
@@ -164,40 +233,54 @@ pub unsafe fn copy_backward(dest: *mut u8, src: *const u8, mut n: usize) {
164233
}
165234
}
166235

236+
/// `n` is in units of bytes, but must be a multiple of the word size and must not be 0.
237+
/// `src` *must not* be `usize`-aligned.
167238
#[cfg(not(feature = "mem-unaligned"))]
168239
#[inline(always)]
169240
unsafe fn copy_backward_misaligned_words(dest: *mut u8, src: *const u8, n: usize) {
241+
debug_assert!(n > 0 && n % WORD_SIZE == 0);
242+
170243
let mut dest_usize = dest as *mut usize;
171-
let dest_start = dest.wrapping_sub(n) as *mut usize;
244+
let dest_start = dest.wrapping_sub(n) as *mut usize; // we're moving towards the start
172245

173246
// Calculate the misalignment offset and shift needed to reassemble value.
247+
// Since `src` is definitely not aligned, `offset` is in the range 1..WORD_SIZE.
174248
let offset = src as usize & WORD_MASK;
175249
let shift = offset * 8;
176250

177-
// Realign src_aligned
178-
let mut src_aligned = (src as usize & !WORD_MASK) as *mut usize;
179-
// This will read (but won't use) bytes out of bound.
180-
// cfg needed because not all targets will have atomic loads that can be lowered
181-
// (e.g. BPF, MSP430), or provided by an external library (e.g. RV32I)
182-
#[cfg(target_has_atomic_load_store = "ptr")]
183-
let mut prev_word = core::intrinsics::atomic_load_unordered(src_aligned);
184-
#[cfg(not(target_has_atomic_load_store = "ptr"))]
185-
let mut prev_word = core::ptr::read_volatile(src_aligned);
251+
// Realign src
252+
let mut src_aligned = src.byte_sub(offset) as *mut usize;
253+
let mut prev_word = load_aligned_partial(src_aligned, offset);
186254

187-
while dest_start < dest_usize {
255+
while dest_start.wrapping_add(1) < dest_usize {
188256
src_aligned = src_aligned.wrapping_sub(1);
189257
let cur_word = *src_aligned;
190258
#[cfg(target_endian = "little")]
191-
let resembled = prev_word << (WORD_SIZE * 8 - shift) | cur_word >> shift;
259+
let reassembled = prev_word << (WORD_SIZE * 8 - shift) | cur_word >> shift;
192260
#[cfg(target_endian = "big")]
193-
let resembled = prev_word >> (WORD_SIZE * 8 - shift) | cur_word << shift;
261+
let reassembled = prev_word >> (WORD_SIZE * 8 - shift) | cur_word << shift;
194262
prev_word = cur_word;
195263

196264
dest_usize = dest_usize.wrapping_sub(1);
197-
*dest_usize = resembled;
265+
*dest_usize = reassembled;
198266
}
267+
268+
// There's one more element left to go, and we can't use the loop for that as on the `src` side,
269+
// it is partially out-of-bounds.
270+
src_aligned = src_aligned.wrapping_sub(1);
271+
let cur_word = load_aligned_end_partial(src_aligned, WORD_SIZE - offset);
272+
#[cfg(target_endian = "little")]
273+
let reassembled = prev_word << (WORD_SIZE * 8 - shift) | cur_word >> shift;
274+
#[cfg(target_endian = "big")]
275+
let reassembled = prev_word >> (WORD_SIZE * 8 - shift) | cur_word << shift;
276+
// prev_word does not matter any more
277+
278+
dest_usize = dest_usize.wrapping_sub(1);
279+
*dest_usize = reassembled;
199280
}
200281

282+
/// `n` is in units of bytes, but must be a multiple of the word size and must not be 0.
283+
/// `src` *must not* be `usize`-aligned.
201284
#[cfg(feature = "mem-unaligned")]
202285
#[inline(always)]
203286
unsafe fn copy_backward_misaligned_words(dest: *mut u8, src: *const u8, n: usize) {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy