Descirbe the bug
time::format_description::parse_strftime_borrowed panics with an index-out-of-bounds error when the format string ends with a padding modifier immediately after %, such as "%_", "%-", or "%0".
Root cause
In time/src/format_description/parse/strftime.rs, the Tokenizer iterator's next() method (line 57) handles the % escape by matching the byte at index 1:
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.input.is_empty() {
return None;
}
....
let (padding, component, advance) = match self.input.get(1) {
Some(&b'_') => (Some(Padding::Space), self.input[2], 3), // line 79
Some(&b'-') => (Some(Padding::None), self.input[2], 3), // line 80
Some(&b'0') => (Some(Padding::Zero), self.input[2], 3), // line 81
Some(_) => (None, self.input[1], 2), // line 82
_ => { // line 83
return Some(Err(error_expected_end(/* ... */)));
}
};
.....
}
When the byte at index 1 is a padding modifier (_, -, 0), the code reads self.input[2] unconditionally to get the component character. If the input is exactly two bytes (e.g. "%_"), index 2 is out of bounds and the access panics.
Reproduction
// examples/poc_strftime_oob_panic.rs
// Run: cargo run --example poc_strftime_oob_panic -p time --features formatting
fn main() {
let inputs = ["%_", "%-", "%0"];
for input in &inputs {
println!("Trying to parse strftime format: {:?}", input);
time::format_description::parse_strftime_borrowed(input);
}
}
Output:
Trying to parse strftime format: "%_"
thread 'main' panicked at time/src/format_description/parse/strftime.rs:79:51:
index out of bounds: the len is 2 but the index is 2
Trying to parse strftime format: "%-"
thread 'main' panicked at time/src/format_description/parse/strftime.rs:80:50:
index out of bounds: the len is 2 but the index is 2
Trying to parse strftime format: "%0"
thread 'main' panicked at time/src/format_description/parse/strftime.rs:81:50:
index out of bounds: the len is 2 but the index is 2
Test environment
- Version: time-rs master (commit: eb08b40)
- OS: Ubuntu 24.04, 64 bit
- Target triple: x86_64-unknown-linux-gnu
- Rustc version: rustc 1.89.0-nightly (5d707b07e 2025-06-02)
Suggested fix
Fix Tokenizer::next() (time/src/format_description/parse/strftime.rs): use .get(2) instead of [2] on the match arms, and return the "unexpected end of input" error when None:
#[inline]
fn next(&mut self) -> Option<Self::Item> {
....
let (padding, component, advance) = match self.input.get(1) {
Some(&b'_') => match self.input.get(2) {
Some(&c) => (Some(Padding::Space), c, 3),
None => return Some(Err(error_expected_end(Location { byte: self.byte_pos }))),
},
Some(&b'-') => match self.input.get(2) {
Some(&c) => (Some(Padding::None), c, 3),
None => return Some(Err(error_expected_end(Location { byte: self.byte_pos }))),
},
Some(&b'0') => match self.input.get(2) {
Some(&c) => (Some(Padding::Zero), c, 3),
None => return Some(Err(error_expected_end(Location { byte: self.byte_pos }))),
},
...
};
...
}
Descirbe the bug
time::format_description::parse_strftime_borrowedpanics with an index-out-of-bounds error when the format string ends with a padding modifier immediately after%, such as"%_","%-", or"%0".Root cause
In
time/src/format_description/parse/strftime.rs, theTokenizeriterator'snext()method (line 57) handles the%escape by matching the byte at index 1:When the byte at index 1 is a padding modifier (
_,-,0), the code readsself.input[2]unconditionally to get the component character. If the input is exactly two bytes (e.g."%_"), index 2 is out of bounds and the access panics.Reproduction
Output:
Test environment
Suggested fix
Fix
Tokenizer::next()(time/src/format_description/parse/strftime.rs): use.get(2)instead of[2]on the match arms, and return the "unexpected end of input" error whenNone: