Skip to content

Delta division and rem_nanos panic on 64-bit but return values on 32-bit #1254

Description

@GeorgeAndrou

While studying the kernel crate I noticed that Delta::rem_nanos() and the Div impl for Delta behave differently depending on CONFIG_64BIT.

Both use native Rust % and / under CONFIG_64BIT, and the C helpers div_s64_rem() / div64_s64() otherwise. rem_nanos() shows the pattern:

linux/rust/kernel/time.rs

Lines 557 to 582 in 28924df

/// Return `self % dividend` where `dividend` is in nanoseconds.
///
/// The kernel doesn't have any emulation for `s64 % s64` on 32 bit platforms, so this is
/// limited to 32 bit dividends.
#[inline]
pub fn rem_nanos(self, dividend: i32) -> Self {
#[cfg(CONFIG_64BIT)]
{
Self {
value: self.as_nanos() % i64::from(dividend),
}
}
#[cfg(not(CONFIG_64BIT))]
{
let mut rem = 0;
// SAFETY: `rem` is in the stack, so we can always provide a valid pointer to it.
unsafe { bindings::div_s64_rem(self.as_nanos(), dividend, &mut rem) };
Self {
value: i64::from(rem),
}
}
}
}

Rust's % and / panic on a zero divisor, and on i64::MIN with a divisor of -1. The C helpers do not, so the same safe call is fatal on
one architecture and returns a value on the other. For Div the panic at least follows from the operator the caller wrote. rem_nanos() is an
ordinary method call, so nothing at the call site suggests potential panicking at runtime.

Tested at 28924df2a08f on x86-64 and ARMv7, calling these from a module init.

On ARMv7:

Call Result
Delta::from_nanos(10).rem_nanos(0) returns 10
Delta::from_nanos(i64::MIN).rem_nanos(-1) returns 0
Delta::from_nanos(10) / Delta::from_nanos(0) returns 0
Delta::from_nanos(i64::MIN) / Delta::from_nanos(-1) returns i64::MIN

Behaviour also depends on the magnitude of the dividend, since do_div() takes different internal paths. Delta::from_nanos(1_i64 << 32).rem_nanos(0) on ARMv7:

[    0.836144] rust_repro: reproducing Delta::rem_nanos zero-divisor path
[    0.836262] Division by zero in kernel.
[    0.836595] CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.3.0-rc2-00006-g28924df2a08f-dirty #6 VOLUNTARY 
[    0.836694] Hardware name: Generic DT based system
[    0.836818] Call trace: 
[    0.837331]  unwind_backtrace from show_stack+0x10/0x14
[    0.837903]  show_stack from dump_stack_lvl+0x34/0x70
[    0.837976]  dump_stack_lvl from Ldiv0_64+0x8/0x18
[    0.838002]  Ldiv0_64 from div_s64_rem+0xa4/0xbc
[    0.838024]  div_s64_rem from __rust_repro_init+0x64/0xc8
[    0.838189] rust_repro: Delta::rem_nanos zero-divisor returned 0

The diagnostic ("Division by zero in kernel." plus a stack trace) is printed and the call still returns. The same call on x86-64 is fatal:

[   13.643086] rust_repro: reproducing Delta::rem_nanos zero-divisor path
[   13.643091] rust_kernel: panicked at rust/kernel/time.rs:566:24:
[   13.643091] attempt to calculate the remainder with a divisor of zero
[   13.643163] ------------[ cut here ]------------
[   13.643166] kernel BUG at rust/helpers/bug.c:7!
...
[   13.677145]  _RNvNtNtCsi1DYhJShKqC_4core9panicking11panic_const23panic_const_rem_by_zero+0x21/0x30
[   13.678434]  _RNvCs4ivlYrUDNg4_10rust_repro24reproduce_delta_rem_zero+0x37/0x40 [rust_repro]
...
[   13.710176] Kernel panic - not syncing: Fatal exception

Delta::from_nanos(1_i64 << 32) / Delta::from_nanos(0) behaves the same way: a diagnostic and a return of 0 on ARMv7, panic_const_div_by_zero at time.rs:415 and a fatal panic on x86-64.

linux/rust/kernel/time.rs

Lines 408 to 424 in 28924df

impl ops::Div for Delta {
type Output = i64;
#[inline]
fn div(self, rhs: Self) -> Self::Output {
#[cfg(CONFIG_64BIT)]
{
self.value / rhs.value
}
#[cfg(not(CONFIG_64BIT))]
{
// SAFETY: This function is always safe to call regardless of the input values
unsafe { bindings::div64_s64(self.value, rhs.value) }
}
}
}

Worth noting: the Div impl carries // SAFETY: This function is always safe to call regardless of the input values. That holds for memory safety, but it sits directly above the 32-bit branch that produces the kernel diagnostic. Also, rem_nanos() documents a portability constraint ("limited to 32 bit dividends") but not these cases, and has no # Panics section.

I don't have a view on which way this should be resolved — making 32-bit panic as 64-bit does, or defining non-panicking behaviour for both — so I'm raising it rather than sending a patch. Either way the resulting semantics would be worth documenting, since these are public kernel crate functions and the current docs say nothing about either condition.

Cc @metaspace @ojeda

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions