Skip to content

Multi::wait() and Multi::poll() panic on valid Duration input due to integer overflow in timeout_i32 #659

Description

@MinghuaWang

Describe the bug

Multi::wait() and Multi::poll() panic with "attempt to add with overflow" when called with a valid Duration with secs == 2_147_483 and nanos >= 648_000_000.

The panic is caused by an off-by-one in the overflow guard inside timeout_i32().

Root cause

timeout_i32 (src/multi.rs:625-633) is meant to convert a Duration to an i32 millisecond count, clamping at i32::MAX:

fn timeout_i32(timeout: Duration) -> i32 {
    let secs = timeout.as_secs();
    if secs > (i32::MAX / 1000) as u64 {    // bug: should use `>=` instead of `>`
        i32::MAX
    } else {
        secs as i32 * 1000 + timeout.subsec_nanos() as i32 / 1_000_000
    }
}

The guard uses > instead of >=. i32::MAX / 1000 == 2_147_483, so secs == 2_147_483 passes through to the else branch. 2_147_483 * 1000 == 2_147_483_000 fits in i32, but when nanos >= 648_000_000, 2_147_483_000 + 648 = 2_147_483_648 > i32::MAX (2_147_483_647), which overflows and causes panic with "attempt to add with overflow".

Affected public APIs

  • Multi::wait()
  • Multi::poll() (feature poll_7_68_0)

Both call timeout_i32() to convert the user-supplied Duration.

To reproduce

use curl::multi::Multi;
use std::time::Duration;

fn main() {
    let m = Multi::new();

    // success: exactly i32::MAX milli secs
    let ok = Duration::new(2_147_483, 647_999_999);
    let r = m.wait(&mut [], ok);
    println!(" ok: {:?}", r.is_ok()); // ok: true

    // panic
    let bad = Duration::new(2_147_483, 648_000_000);
    let _ = m.wait(&mut [], bad);  // panic inside timeout_i32()
}

Run with:

RUST_BACKTRACE=1 cargo run --example poc_timeout_overflow

Stack trace:

thread 'main' (340054) panicked at src/multi.rs:631:13:
attempt to add with overflow
stack backtrace:
   0: __rustc::rust_begin_unwind
             at /rustc/53732d5e076329a62f71d3c6901886ce8a71e812/library/std/src/panicking.rs:690:5
   1: core::panicking::panic_fmt
             at /rustc/53732d5e076329a62f71d3c6901886ce8a71e812/library/core/src/panicking.rs:80:14
   2: core::panicking::panic_const::panic_const_add_overflow
             at /rustc/53732d5e076329a62f71d3c6901886ce8a71e812/library/core/src/panicking.rs:175:17
   3: <curl::multi::Multi>::timeout_i32
             at ./src/multi.rs:631:13
   4: <curl::multi::Multi>::wait
             at ./src/multi.rs:611:26
   5: poc_timeout_overflow::main
             at ./examples/poc_timeout_overflow.rs:13:15

Test environment

  • Version: curl-rust master (commit 0cfd9e3)
  • OS: Ubuntu 24.04, x86_64
  • Rustc: rustc 1.93.0-nightly (53732d5e0 2025-11-20)

Suggested fix

     fn timeout_i32(timeout: Duration) -> i32 {
-        let secs = timeout.as_secs();
-        if secs > (i32::MAX / 1000) as u64 {
-            // Duration too large, clamp at maximum value.
-            i32::MAX
-        } else {
-            secs as i32 * 1000 + timeout.subsec_nanos() as i32 / 1_000_000
-        }
+        timeout.as_millis().min(i32::MAX as u128) as i32
     }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions