Skip to content

Commit b796035

Browse files
authored
fn unaligned_lvl_slice: Safely encapsulate "unaligned" slices of lvl: &[[u8; 4]], as previous accesses were UB (#731)
See: * #728 (comment) * #728 (comment) The current "unaligned" access of `lvl`, added in #726, and a proposed change in #728, [are UB according to `miri`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=80849f822a890cabd324b3b898b291b8), as they cast `&[u8; 3]` and `&u8` to `&[u8; 4]`, which is an out of bounds read of a borrow. This `fn unaligned_lvl_slice` helper `fn` fixes it and passes `miri` by transmuting (through `.align_to`) to a `&[u8]`, doing the slice, and then doing a checked cast back to `[u8; 4]`. It also does correct bounds checking, as the previous ways were off by one in their bounds checking, as they didn't consider the read of the unaligned part into the next `[u8; 4]`. Doing it this way also optimizes just as well as the previous methods. We could also use a dependency like `zerocopy` to encapsulate the `unsafe` `.align_to`, but as long as these kinds of "unaligned" array reads are a one-off, doing it inline here seems better. Update: Switched to using unstable `fn`s from `std` that make things fully safe and correct, copied into our codebase for stability. I already figured out how to fix the UB in reviewing #728, so I just went ahead and made the fix independently.
2 parents a403fa5 + 180f07b commit b796035

3 files changed

Lines changed: 83 additions & 10 deletions

File tree

‎lib.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub mod src {
4646
mod fg_apply;
4747
mod filmgrain;
4848
mod getbits;
49+
mod unstable_extensions;
4950
pub(crate) mod wrap_fn_ptr;
5051
// TODO(kkysen) Temporarily `pub(crate)` due to a `pub use` until TAIT.
5152
pub(super) mod internal;

‎src/lf_apply.rs‎

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use crate::src::lf_mask::Av1Filter;
88
use crate::src::lr_apply::LR_RESTORE_U;
99
use crate::src::lr_apply::LR_RESTORE_V;
1010
use crate::src::lr_apply::LR_RESTORE_Y;
11+
use crate::src::unstable_extensions::as_chunks;
12+
use crate::src::unstable_extensions::flatten;
1113
use libc::ptrdiff_t;
1214
use std::cmp;
1315
use std::ffi::c_int;
@@ -341,6 +343,18 @@ pub(crate) unsafe fn rav1d_copy_lpf<BD: BitDepth>(
341343
}
342344
}
343345

346+
/// Slice `[u8; 4]`s from `lvl`, but "unaligned",
347+
/// meaning the `[u8; 4]`s can straddle
348+
/// adjacent `[u8; 4]`s in the `lvl` slice.
349+
///
350+
/// Note that this does not result in actual unaligned reads,
351+
/// since `[u8; 4]` has an alignment of 1.
352+
/// This optimizes to a single slice with a bounds check.
353+
#[inline(always)]
354+
fn unaligned_lvl_slice(lvl: &[[u8; 4]], y: usize) -> &[[u8; 4]] {
355+
as_chunks(&flatten(lvl)[y..]).0
356+
}
357+
344358
#[inline]
345359
unsafe fn filter_plane_cols_y<BD: BitDepth>(
346360
f: *const Rav1dFrameContext,
@@ -377,7 +391,7 @@ unsafe fn filter_plane_cols_y<BD: BitDepth>(
377391
dst.offset((x * 4) as isize).cast(),
378392
ls,
379393
hmask.as_mut_ptr(),
380-
lvl[x as usize][0..].as_ptr() as *const [u8; 4],
394+
&lvl[x as usize],
381395
b4_stride,
382396
&(*f).lf.lim_lut.0,
383397
endy4 - starty4,
@@ -416,7 +430,7 @@ unsafe fn filter_plane_rows_y<BD: BitDepth>(
416430
dst.cast(),
417431
ls,
418432
vmask.as_ptr(),
419-
lvl[0][1..].as_ptr() as *const [u8; 4],
433+
unaligned_lvl_slice(&lvl[0..], 1).as_ptr(),
420434
b4_stride,
421435
&(*f).lf.lim_lut.0,
422436
w,
@@ -462,7 +476,7 @@ unsafe fn filter_plane_cols_uv<BD: BitDepth>(
462476
u.offset((x * 4) as isize).cast(),
463477
ls,
464478
hmask.as_mut_ptr(),
465-
lvl[x as usize][2..].as_ptr() as *const [u8; 4],
479+
unaligned_lvl_slice(&lvl[x as usize..], 2).as_ptr(),
466480
b4_stride,
467481
&(*f).lf.lim_lut.0,
468482
endy4 - starty4,
@@ -472,7 +486,7 @@ unsafe fn filter_plane_cols_uv<BD: BitDepth>(
472486
v.offset((x * 4) as isize).cast(),
473487
ls,
474488
hmask.as_mut_ptr(),
475-
lvl[x as usize][3..].as_ptr() as *const [u8; 4],
489+
unaligned_lvl_slice(&lvl[x as usize..], 3).as_ptr(),
476490
b4_stride,
477491
&(*f).lf.lim_lut.0,
478492
endy4 - starty4,
@@ -512,7 +526,7 @@ unsafe fn filter_plane_rows_uv<BD: BitDepth>(
512526
u.offset(off_l as isize).cast(),
513527
ls,
514528
vmask.as_ptr(),
515-
lvl[0][2..].as_ptr() as *const [u8; 4],
529+
unaligned_lvl_slice(&lvl[0..], 2).as_ptr(),
516530
b4_stride,
517531
&(*f).lf.lim_lut.0,
518532
w,
@@ -522,7 +536,7 @@ unsafe fn filter_plane_rows_uv<BD: BitDepth>(
522536
v.offset(off_l as isize).cast(),
523537
ls,
524538
vmask.as_ptr(),
525-
lvl[0][3..].as_ptr() as *const [u8; 4],
539+
unaligned_lvl_slice(&lvl[0..], 3).as_ptr(),
526540
b4_stride,
527541
&(*f).lf.lim_lut.0,
528542
w,
@@ -687,10 +701,10 @@ pub(crate) unsafe fn rav1d_loopfilter_sbrow_cols<BD: BitDepth>(
687701
}
688702
}
689703
let mut ptr: *mut BD::Pixel;
690-
let level_ptr = &(*f).lf.level[((*f).b4_stride * sby as isize * sbsz as isize) as usize..];
704+
let mut level_ptr = &(*f).lf.level[((*f).b4_stride * sby as isize * sbsz as isize) as usize..];
691705
ptr = p[0];
692706
have_left = 0 as c_int;
693-
for (x, level_ptr) in (0..(*f).sb128w).zip(level_ptr.chunks(32)) {
707+
for x in 0..(*f).sb128w {
694708
filter_plane_cols_y::<BD>(
695709
f,
696710
have_left,
@@ -705,15 +719,17 @@ pub(crate) unsafe fn rav1d_loopfilter_sbrow_cols<BD: BitDepth>(
705719
);
706720
have_left = 1 as c_int;
707721
ptr = ptr.offset(128);
722+
level_ptr = &level_ptr[32..];
708723
}
709724
if frame_hdr.loopfilter.level_u == 0 && frame_hdr.loopfilter.level_v == 0 {
710725
return;
711726
}
712727
let mut uv_off: ptrdiff_t;
713-
let level_ptr = &(*f).lf.level[((*f).b4_stride * (sby * sbsz >> ss_ver) as isize) as usize..];
728+
let mut level_ptr =
729+
&(*f).lf.level[((*f).b4_stride * (sby * sbsz >> ss_ver) as isize) as usize..];
714730
have_left = 0 as c_int;
715731
uv_off = 0;
716-
for (x, level_ptr) in (0..(*f).sb128w).zip(level_ptr.chunks(32 >> ss_hor)) {
732+
for x in 0..(*f).sb128w {
717733
filter_plane_cols_uv::<BD>(
718734
f,
719735
have_left,
@@ -730,6 +746,7 @@ pub(crate) unsafe fn rav1d_loopfilter_sbrow_cols<BD: BitDepth>(
730746
);
731747
have_left = 1 as c_int;
732748
uv_off += 128 >> ss_hor;
749+
level_ptr = &level_ptr[32 >> ss_hor..];
733750
}
734751
}
735752

‎src/unstable_extensions.rs‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//! Unstable `fn`s copied directly from `std`, with the following differences:
2+
//! * They are free `fn`s now, not methods.
3+
//! * `self` is replaced by `this`.
4+
//! * Things only accessible by `std` are replaced with stable counterparts, such as:
5+
//! * `exact_div` => `/`
6+
//! * `.unchecked_mul` => `*`
7+
//! * `const` `.expect` => `match` and `panic!`
8+
9+
use std::mem;
10+
use std::slice::from_raw_parts;
11+
12+
/// From `1.75.0`.
13+
pub const fn flatten<const N: usize, T>(this: &[[T; N]]) -> &[T] {
14+
let len = if mem::size_of::<T>() == 0 {
15+
match this.len().checked_mul(N) {
16+
None => panic!("slice len overflow"),
17+
Some(it) => it,
18+
}
19+
} else {
20+
// SAFETY: `this.len() * N` cannot overflow because `self` is
21+
// already in the address space.
22+
/* unsafe */
23+
this.len() * N
24+
};
25+
// SAFETY: `[T]` is layout-identical to `[T; N]`
26+
unsafe { from_raw_parts(this.as_ptr().cast(), len) }
27+
}
28+
29+
/// From `1.75.0`.
30+
#[inline]
31+
#[must_use]
32+
pub const unsafe fn as_chunks_unchecked<const N: usize, T>(this: &[T]) -> &[[T; N]] {
33+
// SAFETY: Caller must guarantee that `N` is nonzero and exactly divides the slice length
34+
let new_len = /* unsafe */ {
35+
assert!(N != 0 && this.len() % N == 0);
36+
this.len() / N
37+
};
38+
// SAFETY: We cast a slice of `new_len * N` elements into
39+
// a slice of `new_len` many `N` elements chunks.
40+
unsafe { from_raw_parts(this.as_ptr().cast(), new_len) }
41+
}
42+
43+
/// From `1.75.0`.
44+
#[inline]
45+
#[track_caller]
46+
#[must_use]
47+
pub const fn as_chunks<const N: usize, T>(this: &[T]) -> (&[[T; N]], &[T]) {
48+
assert!(N != 0, "chunk size must be non-zero");
49+
let len = this.len() / N;
50+
let (multiple_of_n, remainder) = this.split_at(len * N);
51+
// SAFETY: We already panicked for zero, and ensured by construction
52+
// that the length of the subslice is a multiple of N.
53+
let array_slice = unsafe { as_chunks_unchecked(multiple_of_n) };
54+
(array_slice, remainder)
55+
}

0 commit comments

Comments
 (0)