1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

uucore: use Duration::saturating_mul in parse_time

Use `Duration::saturating_mul()` to avoid a panic due to overflow in
`uucore::parse_time::from_str()`. This change prevents panic on very
large arguments to timeout and sleep.
This commit is contained in:
Jeffrey Finkelstein 2022-03-17 19:03:26 -04:00
parent 8c36558871
commit 388cb6c83a
3 changed files with 99 additions and 1 deletions

View file

@ -6,11 +6,39 @@
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (vars) NANOS numstr // spell-checker:ignore (vars) NANOS numstr
//! Parsing a duration from a string.
//!
//! Use the [`from_str`] function to parse a [`Duration`] from a string.
use std::time::Duration; use std::time::Duration;
use crate::display::Quotable; use crate::display::Quotable;
/// Parse a duration from a string.
///
/// The string may contain only a number, like "123" or "4.5", or it
/// may contain a number with a unit specifier, like "123s" meaning
/// one hundred twenty three seconds or "4.5d" meaning four and a half
/// days. If no unit is specified, the unit is assumed to be seconds.
///
/// This function uses [`Duration::saturating_mul`] to compute the
/// number of seconds, so it does not overflow. If overflow would have
/// occurred, [`Duration::MAX`] is returned instead.
///
/// # Errors
///
/// This function returns an error if the input string is empty, the
/// input is not a valid number, or the unit specifier is invalid or
/// unknown.
///
/// # Examples
///
/// ```rust
/// use std::time::Duration;
/// use uucore::parse_time::from_str;
/// assert_eq!(from_str("123"), Ok(Duration::from_secs(123)));
/// assert_eq!(from_str("2d"), Ok(Duration::from_secs(60 * 60 * 24 * 2)));
/// ```
pub fn from_str(string: &str) -> Result<Duration, String> { pub fn from_str(string: &str) -> Result<Duration, String> {
let len = string.len(); let len = string.len();
if len == 0 { if len == 0 {
@ -39,5 +67,42 @@ pub fn from_str(string: &str) -> Result<Duration, String> {
let whole_secs = num.trunc(); let whole_secs = num.trunc();
let nanos = (num.fract() * (NANOS_PER_SEC as f64)).trunc(); let nanos = (num.fract() * (NANOS_PER_SEC as f64)).trunc();
let duration = Duration::new(whole_secs as u64, nanos as u32); let duration = Duration::new(whole_secs as u64, nanos as u32);
Ok(duration * times) Ok(duration.saturating_mul(times))
}
#[cfg(test)]
mod tests {
use crate::parse_time::from_str;
use std::time::Duration;
#[test]
fn test_no_units() {
assert_eq!(from_str("123"), Ok(Duration::from_secs(123)));
}
#[test]
fn test_units() {
assert_eq!(from_str("2d"), Ok(Duration::from_secs(60 * 60 * 24 * 2)));
}
#[test]
fn test_saturating_mul() {
assert_eq!(from_str("9223372036854775808d"), Ok(Duration::MAX));
}
#[test]
fn test_error_empty() {
assert!(from_str("").is_err());
}
#[test]
fn test_error_invalid_unit() {
assert!(from_str("123X").is_err());
}
#[test]
fn test_error_invalid_magnitude() {
assert!(from_str("12abc3s").is_err());
}
} }

View file

@ -1,3 +1,4 @@
// spell-checker:ignore dont
use crate::common::util::*; use crate::common::util::*;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -115,3 +116,18 @@ fn test_sleep_sum_duration_many() {
fn test_sleep_wrong_time() { fn test_sleep_wrong_time() {
new_ucmd!().args(&["0.1s", "abc"]).fails(); new_ucmd!().args(&["0.1s", "abc"]).fails();
} }
// TODO These tests would obviously block for a very long time. We
// only want to verify that there is no error here, so we could just
// figure out a way to terminate the child process after a short
// period of time.
// #[test]
#[allow(dead_code)]
fn test_dont_overflow() {
new_ucmd!()
.arg("9223372036854775808d")
.succeeds()
.no_stderr()
.no_stdout();
}

View file

@ -1,3 +1,4 @@
// spell-checker:ignore dont
use crate::common::util::*; use crate::common::util::*;
// FIXME: this depends on the system having true and false in PATH // FIXME: this depends on the system having true and false in PATH
@ -64,3 +65,19 @@ fn test_preserve_status() {
.no_stderr() .no_stderr()
.no_stdout(); .no_stdout();
} }
#[test]
fn test_dont_overflow() {
new_ucmd!()
.args(&["9223372036854775808d", "sleep", "0"])
.succeeds()
.code_is(0)
.no_stderr()
.no_stdout();
new_ucmd!()
.args(&["-k", "9223372036854775808d", "10", "sleep", "0"])
.succeeds()
.code_is(0)
.no_stderr()
.no_stdout();
}