Skip to content

Commit 3a2f7c5

Browse files
committed
fix Page Step impl on 32-bit platforms
Previously, we scaled count as a usize, not a u64. This is bad because stepping forward by 0x10_0000 is perfectly fine, yet we were always rejecting this because 0x10_0000 * 0x1000 doesn't fit in usize on 32-bit platforms. Similarly, we would fail to return a step count above or equal to 0x10_0000 because the call to <VirtAddr as Step>::steps_between would fail. Never scale usize values, instead, always scale u64 values. To make this easier to work with, this patch adds variants of the Step functions to VirtAddr that take and return u64 instead of usize. This patch also adds some regression tests.
1 parent 9fb6a53 commit 3a2f7c5

File tree

2 files changed

+180
-37
lines changed

2 files changed

+180
-37
lines changed

src/addr.rs

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -242,28 +242,41 @@ impl VirtAddr {
242242
// FIXME: Move this into the `Step` impl, once `Step` is stabilized.
243243
#[cfg(any(feature = "instructions", feature = "step_trait"))]
244244
pub(crate) fn steps_between_impl(start: &Self, end: &Self) -> (usize, Option<usize>) {
245-
let mut steps = if let Some(steps) = end.0.checked_sub(start.0) {
246-
steps
245+
if let Some(steps) = Self::steps_between_u64(start, end) {
246+
let steps = usize::try_from(steps).ok();
247+
(steps.unwrap_or(!0), steps)
247248
} else {
248-
return (0, None);
249-
};
249+
(0, None)
250+
}
251+
}
252+
253+
/// An implementation of steps_between that returns u64. Note that this
254+
/// function always returns the exact bound, so it doesn't need to return a
255+
/// lower and upper bound like steps_between does.
256+
#[cfg(any(feature = "instructions", feature = "step_trait"))]
257+
pub(crate) fn steps_between_u64(start: &Self, end: &Self) -> Option<u64> {
258+
let mut steps = end.0.checked_sub(start.0)?;
250259

251260
// Mask away extra bits that appear while jumping the gap.
252261
steps &= 0xffff_ffff_ffff;
253262

254-
let steps = usize::try_from(steps).ok();
255-
(steps.unwrap_or(!0), steps)
263+
Some(steps)
256264
}
257265

258266
// FIXME: Move this into the `Step` impl, once `Step` is stabilized.
259267
#[inline]
260268
pub(crate) fn forward_checked_impl(start: Self, count: usize) -> Option<Self> {
261-
let offset = u64::try_from(count).ok()?;
262-
if offset > ADDRESS_SPACE_SIZE {
269+
Self::forward_checked_u64(start, u64::try_from(count).ok()?)
270+
}
271+
272+
/// An implementation of forward_checked that takes u64 instead of usize.
273+
#[inline]
274+
pub(crate) fn forward_checked_u64(start: Self, count: u64) -> Option<Self> {
275+
if count > ADDRESS_SPACE_SIZE {
263276
return None;
264277
}
265278

266-
let mut addr = start.0.checked_add(offset)?;
279+
let mut addr = start.0.checked_add(count)?;
267280

268281
match addr.get_bits(47..) {
269282
0x1 => {
@@ -279,6 +292,30 @@ impl VirtAddr {
279292

280293
Some(unsafe { Self::new_unsafe(addr) })
281294
}
295+
296+
/// An implementation of backward_checked that takes u64 instead of usize.
297+
#[inline]
298+
pub(crate) fn backward_checked_u64(start: Self, count: u64) -> Option<Self> {
299+
if count > ADDRESS_SPACE_SIZE {
300+
return None;
301+
}
302+
303+
let mut addr = start.0.checked_sub(count)?;
304+
305+
match addr.get_bits(47..) {
306+
0x1fffe => {
307+
// Jump the gap by sign extending the 47th bit.
308+
addr.set_bits(47.., 0);
309+
}
310+
0x1fffd => {
311+
// Address underflow
312+
return None;
313+
}
314+
_ => {}
315+
}
316+
317+
Some(unsafe { Self::new_unsafe(addr) })
318+
}
282319
}
283320

284321
impl fmt::Debug for VirtAddr {
@@ -376,26 +413,7 @@ impl Step for VirtAddr {
376413

377414
#[inline]
378415
fn backward_checked(start: Self, count: usize) -> Option<Self> {
379-
let offset = u64::try_from(count).ok()?;
380-
if offset > ADDRESS_SPACE_SIZE {
381-
return None;
382-
}
383-
384-
let mut addr = start.0.checked_sub(offset)?;
385-
386-
match addr.get_bits(47..) {
387-
0x1fffe => {
388-
// Jump the gap by sign extending the 47th bit.
389-
addr.set_bits(47.., 0);
390-
}
391-
0x1fffd => {
392-
// Address underflow
393-
return None;
394-
}
395-
_ => {}
396-
}
397-
398-
Some(unsafe { Self::new_unsafe(addr) })
416+
Self::backward_checked_u64(start, u64::try_from(count).ok()?)
399417
}
400418
}
401419

@@ -779,6 +797,18 @@ mod tests {
779797
),
780798
(0, None)
781799
);
800+
// Make sure that we handle `steps * PAGE_SIZE > u32::MAX`
801+
// correctly on 32-bit targets.
802+
#[cfg(target_pointer_width = "64")]
803+
assert_eq!(
804+
Step::steps_between(&VirtAddr(0), &VirtAddr(0x1_0000_0000)),
805+
(0x1_0000_0000, Some(0x1_0000_0000))
806+
);
807+
#[cfg(not(target_pointer_width = "64"))]
808+
assert_eq!(
809+
Step::steps_between(&VirtAddr(0), &VirtAddr(0x1_0000_0000)),
810+
(!0, None)
811+
);
782812
}
783813

784814
#[test]

src/structures/paging/page.rs

Lines changed: 121 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,26 @@ impl<S: PageSize> Page<S> {
161161
// FIXME: Move this into the `Step` impl, once `Step` is stabilized.
162162
#[cfg(any(feature = "instructions", feature = "step_trait"))]
163163
pub(crate) fn steps_between_impl(start: &Self, end: &Self) -> (usize, Option<usize>) {
164-
let (lower, upper) = VirtAddr::steps_between_impl(&start.start_address, &end.start_address);
165-
let lower = lower / S::SIZE as usize;
166-
let upper = upper.map(|steps| steps / S::SIZE as usize);
167-
(lower, upper)
164+
use core::convert::TryFrom;
165+
166+
if let Some(steps) =
167+
VirtAddr::steps_between_u64(&start.start_address(), &end.start_address())
168+
{
169+
let steps = steps / S::SIZE;
170+
let steps = usize::try_from(steps).ok();
171+
(steps.unwrap_or(!0), steps)
172+
} else {
173+
(0, None)
174+
}
168175
}
169176

170177
// FIXME: Move this into the `Step` impl, once `Step` is stabilized.
171178
#[cfg(any(feature = "instructions", feature = "step_trait"))]
172179
pub(crate) fn forward_checked_impl(start: Self, count: usize) -> Option<Self> {
173-
let count = count.checked_mul(S::SIZE as usize)?;
174-
let start_address = VirtAddr::forward_checked_impl(start.start_address, count)?;
180+
use core::convert::TryFrom;
181+
182+
let count = u64::try_from(count).ok()?.checked_mul(S::SIZE)?;
183+
let start_address = VirtAddr::forward_checked_u64(start.start_address, count)?;
175184
Some(Self {
176185
start_address,
177186
size: PhantomData,
@@ -304,8 +313,10 @@ impl<S: PageSize> Step for Page<S> {
304313
}
305314

306315
fn backward_checked(start: Self, count: usize) -> Option<Self> {
307-
let count = count.checked_mul(S::SIZE as usize)?;
308-
let start_address = Step::backward_checked(start.start_address, count)?;
316+
use core::convert::TryFrom;
317+
318+
let count = u64::try_from(count).ok()?.checked_mul(S::SIZE)?;
319+
let start_address = VirtAddr::backward_checked_u64(start.start_address, count)?;
309320
Some(Self {
310321
start_address,
311322
size: PhantomData,
@@ -531,4 +542,106 @@ mod tests {
531542
let range_inclusive = PageRangeInclusive { start, end };
532543
assert_eq!(range_inclusive.len(), 51);
533544
}
545+
546+
#[test]
547+
#[cfg(feature = "step_trait")]
548+
fn page_step_forward() {
549+
let test_cases = [
550+
(0, 0, Some(0)),
551+
(0, 1, Some(0x1000)),
552+
(0x1000, 1, Some(0x2000)),
553+
(0x7fff_ffff_f000, 1, Some(0xffff_8000_0000_0000)),
554+
(0xffff_8000_0000_0000, 1, Some(0xffff_8000_0000_1000)),
555+
(0xffff_ffff_ffff_f000, 1, None),
556+
#[cfg(target_pointer_width = "64")]
557+
(0x7fff_ffff_f000, 0x1_2345_6789, Some(0xffff_9234_5678_8000)),
558+
#[cfg(target_pointer_width = "64")]
559+
(0x7fff_ffff_f000, 0x8_0000_0000, Some(0xffff_ffff_ffff_f000)),
560+
#[cfg(target_pointer_width = "64")]
561+
(0x7fff_fff0_0000, 0x8_0000_00ff, Some(0xffff_ffff_ffff_f000)),
562+
#[cfg(target_pointer_width = "64")]
563+
(0x7fff_fff0_0000, 0x8_0000_0100, None),
564+
#[cfg(target_pointer_width = "64")]
565+
(0x7fff_ffff_f000, 0x8_0000_0001, None),
566+
// Make sure that we handle `steps * PAGE_SIZE > u32::MAX`
567+
// correctly on 32-bit targets.
568+
(0, 0x10_0000, Some(0x1_0000_0000)),
569+
];
570+
for (start, count, result) in test_cases {
571+
let start = Page::<Size4KiB>::from_start_address(VirtAddr::new(start)).unwrap();
572+
let result = result
573+
.map(|result| Page::<Size4KiB>::from_start_address(VirtAddr::new(result)).unwrap());
574+
assert_eq!(Step::forward_checked(start, count), result);
575+
}
576+
}
577+
578+
#[test]
579+
#[cfg(feature = "step_trait")]
580+
fn page_step_backwards() {
581+
let test_cases = [
582+
(0, 0, Some(0)),
583+
(0, 1, None),
584+
(0x1000, 1, Some(0)),
585+
(0xffff_8000_0000_0000, 1, Some(0x7fff_ffff_f000)),
586+
(0xffff_8000_0000_1000, 1, Some(0xffff_8000_0000_0000)),
587+
#[cfg(target_pointer_width = "64")]
588+
(0xffff_9234_5678_8000, 0x1_2345_6789, Some(0x7fff_ffff_f000)),
589+
#[cfg(target_pointer_width = "64")]
590+
(0xffff_8000_0000_0000, 0x8_0000_0000, Some(0)),
591+
#[cfg(target_pointer_width = "64")]
592+
(0xffff_8000_0000_0000, 0x7_ffff_ff01, Some(0xff000)),
593+
#[cfg(target_pointer_width = "64")]
594+
(0xffff_8000_0000_0000, 0x8_0000_0001, None),
595+
// Make sure that we handle `steps * PAGE_SIZE > u32::MAX`
596+
// correctly on 32-bit targets.
597+
(0x1_0000_0000, 0x10_0000, Some(0)),
598+
];
599+
for (start, count, result) in test_cases {
600+
let start = Page::<Size4KiB>::from_start_address(VirtAddr::new(start)).unwrap();
601+
let result = result
602+
.map(|result| Page::<Size4KiB>::from_start_address(VirtAddr::new(result)).unwrap());
603+
assert_eq!(Step::backward_checked(start, count), result);
604+
}
605+
}
606+
607+
#[test]
608+
#[cfg(feature = "step_trait")]
609+
fn page_steps_between() {
610+
let test_cases = [
611+
(0, 0, 0, Some(0)),
612+
(0, 0x1000, 1, Some(1)),
613+
(0x1000, 0, 0, None),
614+
(0x1000, 0x1000, 0, Some(0)),
615+
(0x7fff_ffff_f000, 0xffff_8000_0000_0000, 1, Some(1)),
616+
(0xffff_8000_0000_0000, 0x7fff_ffff_f000, 0, None),
617+
(0xffff_8000_0000_0000, 0xffff_8000_0000_0000, 0, Some(0)),
618+
(0xffff_8000_0000_0000, 0xffff_8000_0000_1000, 1, Some(1)),
619+
(0xffff_8000_0000_1000, 0xffff_8000_0000_0000, 0, None),
620+
(0xffff_8000_0000_1000, 0xffff_8000_0000_1000, 0, Some(0)),
621+
// Make sure that we handle `steps > u32::MAX` correctly on 32-bit
622+
// targets.
623+
(
624+
0x0000_0000_0000,
625+
0x0001_0000_0000,
626+
0x10_0000,
627+
Some(0x10_0000),
628+
),
629+
// The returned bounds are different when `steps` doesn't fit in
630+
// into `usize`.
631+
#[cfg(target_pointer_width = "64")]
632+
(
633+
0x0000_0000_0000,
634+
0x1000_0000_0000,
635+
0x1_0000_0000,
636+
Some(0x1_0000_0000),
637+
),
638+
#[cfg(not(target_pointer_width = "64"))]
639+
(0x0000_0000_0000, 0x1000_0000_0000, !0, None),
640+
];
641+
for (start, end, lower, upper) in test_cases {
642+
let start = Page::<Size4KiB>::from_start_address(VirtAddr::new(start)).unwrap();
643+
let end = Page::from_start_address(VirtAddr::new(end)).unwrap();
644+
assert_eq!(Step::steps_between(&start, &end), (lower, upper));
645+
}
646+
}
534647
}

0 commit comments

Comments
 (0)