Skip to content

parse_strftime_borrowed panics on truncated padding modifier ("%_", "%-", "%0") #781

@MinghuaWang

Description

@MinghuaWang

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 }))),
        },
        ...
    };
...
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions