Skip to content

Fix subtraction overflow in ExtendedTimestamp::try_from_reader#697

Closed
ozzieba wants to merge 1 commit intozip-rs:masterfrom
ozzieba:fix-extended-timestamp-overflow
Closed

Fix subtraction overflow in ExtendedTimestamp::try_from_reader#697
ozzieba wants to merge 1 commit intozip-rs:masterfrom
ozzieba:fix-extended-timestamp-overflow

Conversation

@ozzieba
Copy link
Copy Markdown

@ozzieba ozzieba commented Feb 26, 2026

Summary

Fixes #696 -- ExtendedTimestamp::try_from_reader can panic with "attempt to subtract with overflow" when processing malformed ZIP files where the extended timestamp extra field length is inconsistent with the flags byte.

Changes

  • Replace three unchecked bytes_to_read -= size_of::<u32>() subtractions with checked_sub that returns ZipError::InvalidArchive on underflow
  • Add unit test test_extended_timestamp_overflow that verifies truncated extended timestamp fields return an error instead of panicking

Details

The existing validation check (len != 5 && len != 1 + 4 * flags.count_ones()) catches most inconsistent inputs before reaching the subtraction. However, using checked arithmetic provides defense-in-depth against:

  • Future refactors that might weaken the validation
  • Edge cases in the interaction between the len == 5 bypass and flag-based branching

This is the idiomatic Rust approach for arithmetic on untrusted input.

Test plan

  • cargo test --lib extra_fields::extended_timestamp -- both existing and new test pass
  • cargo test -- full test suite passes (189 tests, 0 failures)

Replace unchecked `bytes_to_read -= size_of::<u32>()` with
`checked_sub` that returns `ZipError::InvalidArchive` instead of
panicking on malformed ZIP files where the extended timestamp extra
field length is inconsistent with the flags byte.

While the existing validation check (`len != 5 && len != 1 + 4 *
flags.count_ones()`) catches most inconsistent inputs before reaching
the subtraction, using checked arithmetic provides defense-in-depth
against future refactors that might weaken the validation, and is the
idiomatic Rust approach for arithmetic on untrusted input.

Fixes zip-rs#696
Copilot AI review requested due to automatic review settings February 26, 2026 00:56
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @ozzieba, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the robustness of the ExtendedTimestamp parsing logic by addressing a potential subtraction overflow vulnerability. By transitioning from unchecked arithmetic to checked_sub operations, the system is now designed to gracefully handle malformed ZIP file inputs that previously could lead to application panics. This change ensures more resilient error handling and improves overall stability when dealing with untrusted data.

Highlights

  • Robustness Improvement: Replaced unchecked subtractions with checked_sub in ExtendedTimestamp::try_from_reader to prevent panics from subtraction overflow when processing malformed ZIP files.
  • Error Handling: Introduced specific error returns (ZipError::InvalidArchive) when extended timestamp fields are found to be too short during parsing.
  • Unit Testing: Added a new unit test, test_extended_timestamp_overflow, to verify that truncated extended timestamp fields correctly return an error instead of causing a panic.
Changelog
  • src/extra_fields/extended_timestamp.rs
    • Replaced direct subtraction assignments for bytes_to_read with checked_sub calls, ensuring arithmetic safety.
    • Added ok_or clauses to checked_sub results, converting potential underflows into ZipError::InvalidArchive.
    • Introduced test_extended_timestamp_overflow to validate the new error handling for truncated timestamp fields.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@Its-Just-Nans
Copy link
Copy Markdown
Member

Could you provide a file example

Thanks

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly addresses a potential panic due to subtraction overflow by using checked_sub. The change is robust and includes a new unit test to verify the fix. I have provided one suggestion to refactor the timestamp parsing logic to improve code clarity and maintainability by reducing duplication.

Comment on lines 55 to 79
let mod_time = if (flags & 0b0000_0001_u8 == 0b0000_0001_u8) || len == 5 {
bytes_to_read -= size_of::<u32>();
bytes_to_read = bytes_to_read
.checked_sub(size_of::<u32>())
.ok_or(invalid!("Extended timestamp field too short for mod_time"))?;
Some(reader.read_u32_le()?)
} else {
None
};

let ac_time = if flags & 0b0000_0010_u8 == 0b0000_0010_u8 && len > 5 {
bytes_to_read -= size_of::<u32>();
bytes_to_read = bytes_to_read
.checked_sub(size_of::<u32>())
.ok_or(invalid!("Extended timestamp field too short for ac_time"))?;
Some(reader.read_u32_le()?)
} else {
None
};

let cr_time = if flags & 0b0000_0100_u8 == 0b0000_0100_u8 && len > 5 {
bytes_to_read -= size_of::<u32>();
bytes_to_read = bytes_to_read
.checked_sub(size_of::<u32>())
.ok_or(invalid!("Extended timestamp field too short for cr_time"))?;
Some(reader.read_u32_le()?)
} else {
None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While the change is correct, there is significant code duplication in parsing the mod_time, ac_time, and cr_time. This can be refactored to improve readability and maintainability by defining constants for the flags and using a helper closure to read the timestamp values.

        const MOD_TIME_FLAG: u8 = 1;
        const AC_TIME_FLAG: u8 = 2;
        const CR_TIME_FLAG: u8 = 4;

        let mut read_timestamp = |field_name: &'static str| -> ZipResult<u32> {
            bytes_to_read = bytes_to_read
                .checked_sub(std::mem::size_of::<u32>())
                .ok_or(invalid!(
                    "Extended timestamp field too short for {}",
                    field_name
                ))?;
            reader.read_u32_le()
        };

        let mod_time = if (flags & MOD_TIME_FLAG != 0) || len == 5 {
            Some(read_timestamp("mod_time")?)
        } else {
            None
        };

        let ac_time = if (flags & AC_TIME_FLAG != 0) && len > 5 {
            Some(read_timestamp("ac_time")?)
        } else {
            None
        };

        let cr_time = if (flags & CR_TIME_FLAG != 0) && len > 5 {
            Some(read_timestamp("cr_time")?)
        } else {
            None
        }

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens parsing of the ZIP “extended timestamp” extra field to avoid panics on malformed input by replacing unchecked arithmetic with checked underflow handling, and adds a regression test around truncated inputs.

Changes:

  • Replace three bytes_to_read -= ... subtractions with checked_sub(...) that returns an error on underflow.
  • Add a unit test covering a truncated/invalid extended timestamp extra field.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 55 to +58
let mod_time = if (flags & 0b0000_0001_u8 == 0b0000_0001_u8) || len == 5 {
bytes_to_read -= size_of::<u32>();
bytes_to_read = bytes_to_read
.checked_sub(size_of::<u32>())
.ok_or(invalid!("Extended timestamp field too short for mod_time"))?;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size_of::<u32>() is used here but size_of isn’t in scope in this module, which will fail to compile. Import it (e.g., use core::mem::size_of;) or fully-qualify the calls (e.g., core::mem::size_of::<u32>()).

Copilot uses AI. Check for mistakes.
// but len = 2 only provides 1 byte after the flags byte.
// The validation check catches this before the subtraction, but even
// if validation were removed, checked_sub would catch it.
let data: &[u8] = &[0x01, 0x00, 0x00, 0x00];
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test’s comment says len = 2 “only provides 1 byte after the flags byte”, but data currently contains 4 bytes total. Consider making the buffer length consistent with the declared len (e.g., only flags + 1 byte) so the test actually represents a truncated extra field and won’t accidentally pass if code starts reading past len.

Suggested change
let data: &[u8] = &[0x01, 0x00, 0x00, 0x00];
let data: &[u8] = &[0x01, 0x00];

Copilot uses AI. Check for mistakes.
@ozzieba ozzieba closed this Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Panic: subtraction overflow in ExtendedTimestamp::try_from_reader (v4.6.1)

3 participants