Skip to content

Commit 1221370

Browse files
fixed both overflow issues. Main duration overflow & Kill-after duration overflow
1 parent a4c5df6 commit 1221370

File tree

1 file changed

+45
-46
lines changed

1 file changed

+45
-46
lines changed

src/uu/timeout/src/timeout.rs

Lines changed: 45 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,49 @@ struct Config {
5757
command: Vec<String>,
5858
}
5959

60+
/// Parse a duration string with overflow protection
61+
/// Caps extremely large values at a safe maximum that works on all platforms
62+
fn parse_duration_with_overflow_protection(duration_str: &str) -> UResult<Duration> {
63+
// Find where the unit suffix starts (first non-digit character)
64+
let numeric_end = duration_str
65+
.find(|c: char| !c.is_ascii_digit())
66+
.unwrap_or(duration_str.len());
67+
let numeric_part = &duration_str[..numeric_end];
68+
let unit_suffix = &duration_str[numeric_end..];
69+
70+
if let Ok(num) = numeric_part.parse::<u128>() {
71+
match unit_suffix {
72+
"" | "s" | "m" | "h" | "d" => {
73+
let (multiplier, max_safe) = match unit_suffix {
74+
"" | "s" => (1u64, u64::MAX),
75+
"m" => (60, u64::MAX / 60),
76+
"h" => (3600, u64::MAX / 3600),
77+
"d" => (86400, u64::MAX / 86400),
78+
_ => unreachable!(),
79+
};
80+
81+
if num > max_safe as u128 {
82+
// Cap at a safe maximum (~34 years) that works on all platforms
83+
// This matches the cap in process.rs for kqueue/sigtimedwait
84+
const MAX_SAFE_TIMEOUT_SECS: u64 = (i32::MAX / 2) as u64;
85+
Ok(Duration::from_secs(MAX_SAFE_TIMEOUT_SECS))
86+
} else {
87+
let secs = (num as u64) * multiplier;
88+
Ok(Duration::from_secs(secs))
89+
}
90+
}
91+
_ => {
92+
// Unknown suffix, fallback to parse_time
93+
parse_time::from_str(duration_str, true)
94+
.map_err(|err| UUsageError::new(ExitStatus::TimeoutFailed.into(), err))
95+
}
96+
}
97+
} else {
98+
parse_time::from_str(duration_str, true)
99+
.map_err(|err| UUsageError::new(ExitStatus::TimeoutFailed.into(), err))
100+
}
101+
}
102+
60103
impl Config {
61104
fn from(options: &clap::ArgMatches) -> UResult<Self> {
62105
let signal = match options.get_one::<String>(options::SIGNAL) {
@@ -77,55 +120,11 @@ impl Config {
77120

78121
let kill_after = match options.get_one::<String>(options::KILL_AFTER) {
79122
None => None,
80-
Some(kill_after) => match parse_time::from_str(kill_after, true) {
81-
Ok(k) => Some(k),
82-
Err(err) => return Err(UUsageError::new(ExitStatus::TimeoutFailed.into(), err)),
83-
},
123+
Some(kill_after_str) => Some(parse_duration_with_overflow_protection(kill_after_str)?),
84124
};
85125

86-
// Pre-validate duration string to prevent overflow in parse_time
87126
let duration_str = options.get_one::<String>(options::DURATION).unwrap();
88-
let duration = {
89-
// Find where the unit suffix starts (first non-digit character)
90-
let numeric_end = duration_str
91-
.find(|c: char| !c.is_ascii_digit())
92-
.unwrap_or(duration_str.len());
93-
let numeric_part = &duration_str[..numeric_end];
94-
let unit_suffix = &duration_str[numeric_end..];
95-
96-
if let Ok(num) = numeric_part.parse::<u128>() {
97-
match unit_suffix {
98-
"" | "s" | "m" | "h" | "d" => {
99-
let (multiplier, max_safe) = match unit_suffix {
100-
"" | "s" => (1u64, u64::MAX),
101-
"m" => (60, u64::MAX / 60),
102-
"h" => (3600, u64::MAX / 3600),
103-
"d" => (86400, u64::MAX / 86400),
104-
_ => unreachable!(),
105-
};
106-
107-
if num > max_safe as u128 {
108-
// Cap at a safe maximum (~34 years) that works on all platforms
109-
// This matches the cap in process.rs for kqueue/sigtimedwait
110-
const MAX_SAFE_TIMEOUT_SECS: u64 = (i32::MAX / 2) as u64;
111-
Duration::from_secs(MAX_SAFE_TIMEOUT_SECS)
112-
} else {
113-
let secs = (num as u64) * multiplier;
114-
Duration::from_secs(secs)
115-
}
116-
}
117-
_ => {
118-
// Unknown suffix, fallback to parse_time
119-
parse_time::from_str(duration_str, true).map_err(|err| {
120-
UUsageError::new(ExitStatus::TimeoutFailed.into(), err)
121-
})?
122-
}
123-
}
124-
} else {
125-
parse_time::from_str(duration_str, true)
126-
.map_err(|err| UUsageError::new(ExitStatus::TimeoutFailed.into(), err))?
127-
}
128-
};
127+
let duration = parse_duration_with_overflow_protection(duration_str)?;
129128

130129
let preserve_status: bool = options.get_flag(options::PRESERVE_STATUS);
131130
let foreground = options.get_flag(options::FOREGROUND);

0 commit comments

Comments
 (0)