Skip to content

fix: assorted numeric overflow fixes - #2462

Open
andrewwhitecdw wants to merge 9 commits into
NVIDIA:mainfrom
andrewwhitecdw:fix-assorted-arithmetic-overflows/aw
Open

fix: assorted numeric overflow fixes#2462
andrewwhitecdw wants to merge 9 commits into
NVIDIA:mainfrom
andrewwhitecdw:fix-assorted-arithmetic-overflows/aw

Conversation

@andrewwhitecdw

@andrewwhitecdw andrewwhitecdw commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Eight small, independent numeric overflow/underflow fixes in production code paths that handle user or external configuration values:

  1. server: SSH session TTL calculation cast a u64 to i64 and multiplied by 1000, overflowing for large configured TTLs.
  2. driver-podman: Health-check interval multiplied a u64 seconds value by 1_000_000_000, overflowing for huge intervals.
  3. cli: parse_duration_to_ms multiplied the parsed i64 by a fixed multiplier, overflowing for inputs like 100000000000000h.
  4. cli: openshell logs --since <duration> subtracted the parsed duration from now_ms, underflowing when the duration exceeded the current Unix epoch.
  5. tui: format_age subtracted created_secs from now before checking for a future timestamp, so clock-skewed/future values underflowed before the guard could return "-".
  6. driver-podman: parse_cpu_to_microseconds checked that cores was finite, but cores * 100_000 could overflow to infinity and saturate to u64::MAX.
  7. driver-docker: parse_cpu_limit multiplied parsed cores by 1_000_000_000; huge inputs overflowed to infinity and saturated to i64::MAX.
  8. driver-docker: parse_memory_limit multiplied parsed amount by a suffix multiplier; huge inputs overflowed to infinity and saturated to i64::MAX.

Related Issue

N/A — small fixes found during code review.

Changes

  • sandbox.rs: i64::try_from(...).unwrap_or(i64::MAX).saturating_mul(1000), then saturating_add
  • container.rs (podman): saturating_mul(1_000_000_000) for health-check interval; reject non-finite or oversized CPU quota products
  • commands/common.rs: checked_mul in parse_duration_to_ms; regression test
  • run.rs: saturating_sub for --since start timestamp
  • lib.rs (tui): guard created_secs > now before subtracting in format_age; regression tests
  • lib.rs + tests.rs (docker): reject non-finite or out-of-range CPU/memory limit products; regression tests

Testing

  • mise run pre-commit passes (mise unavailable in this environment; ran equivalent cargo fmt + cargo clippy --all-targets on all touched crates — clean)
  • Unit tests added/updated and passing:
    • cargo test -p openshell-cli parse_duration_to_ms_rejects_overflow → passed
    • cargo test -p openshell-tui format_age → 2 passed
    • cargo test -p openshell-driver-podman parse_cpu → 5 passed
    • cargo test -p openshell-driver-docker parse_cpu_limit → 2 passed
    • cargo test -p openshell-driver-docker parse_memory_limit → 2 passed
  • E2E tests added/updated (if applicable)

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable)

@copy-pr-bot

copy-pr-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@andrewwhitecdw

Copy link
Copy Markdown
Contributor Author

I have read the DCO document and I hereby sign the DCO.

@andrewwhitecdw andrewwhitecdw changed the title fix: assorted signed/unsigned integer overflow fixes fix: assorted numeric overflow fixes Jul 24, 2026

@johntmyers johntmyers left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

gator-agent

PR Review Status

Validation: This is project-valid small, concentrated numeric-safety work across existing CLI, TUI, server, Docker, and Podman paths.
Head SHA: 260664b542b829e612093b7ed7e617192daed804

Review findings:

  • The Docker millicore path still uses saturating_mul(1_000_000), so oversized millicore input silently clamps while the equivalent core input errors. Please use checked multiplication, return the same “too large” error, and add a regression test.
  • Four line-specific boundary findings are included inline.

Docs: No Fern update is needed for the current arithmetic-only changes. If remediation introduces a documented maximum Podman health-check interval, update docs/reference/gateway-config.mdx.

E2E: Deferred until review findings are resolved; the current patch is arithmetic-only and focused boundary unit tests are sufficient.

Next state: gator:in-review

Ok(Some((cores * 1_000_000_000.0).round() as i64))
let nano_cpus = (cores * 1_000_000_000.0).round();
#[allow(clippy::cast_precision_loss)]
if !nano_cpus.is_finite() || nano_cpus < i64::MIN as f64 || nano_cpus > i64::MAX as f64 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

gator-agent

Warning (CWE-190): i64::MAX as f64 rounds to 2^63, so an intermediate equal to 2^63 passes this > check and the cast silently saturates to i64::MAX. Reject this boundary (for example with >=) or use an exact checked conversion, and add a 2^63 regression test.

Ok(Some((amount * multiplier).round() as i64))
let bytes = (amount * multiplier).round();
#[allow(clippy::cast_precision_loss)]
if !bytes.is_finite() || bytes < i64::MIN as f64 || bytes > i64::MAX as f64 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

gator-agent

Warning (CWE-190): The same 2^63 boundary issue applies here: a quantity producing exactly 2^63 bytes passes validation and clamps to i64::MAX. Reject equality or use an exact checked conversion, with a boundary regression test.

}
let micros_f = cores * 100_000.0;
#[allow(clippy::cast_precision_loss)]
if !micros_f.is_finite() || micros_f > u64::MAX as f64 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

gator-agent

Warning (CWE-190): u64::MAX as f64 rounds to 2^64, so an intermediate equal to 2^64 passes this check and the cast saturates to u64::MAX. Reject this boundary (for example with >=) or use checked exact parsing, and test the 2^64 boundary.

interval: config.health_check_interval_secs * 1_000_000_000,
interval: config
.health_check_interval_secs
.saturating_mul(1_000_000_000),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

gator-agent

Warning (CWE-190): saturating_mul converts overflow to u64::MAX, but Podman’s inherited health-check interval uses a signed Go time.Duration in nanoseconds. Invalid configuration can therefore survive startup and fail later during sandbox creation. Validate the interval against i64::MAX / 1_000_000_000 during driver initialization, use checked multiplication, and add a boundary test.

@johntmyers johntmyers added the gator:in-review Gator is reviewing or awaiting PR review feedback label Jul 24, 2026
@johntmyers

Copy link
Copy Markdown
Collaborator

/ok to test 260664b

andrewwhitecdw added a commit to andrewwhitecdw/OpenShell that referenced this pull request Jul 28, 2026
Addresses gator-agent review feedback on NVIDIA#2462:

- Docker parse_cpu_limit / parse_memory_limit: use >= i64::MAX as f64 so inputs rounding to 2^63 are rejected instead of saturating.

- Podman parse_cpu_to_microseconds: use >= u64::MAX as f64 so inputs rounding to 2^64 are rejected instead of saturating.

- Podman health-check interval: replace saturating_mul with checked_mul and reject values exceeding i64::MAX nanoseconds.

- Add regression tests for the i64::MAX, u64::MAX, and health-check interval boundaries.

Signed-off-by: Andrew White <andrewh@cdw.com>

@johntmyers johntmyers left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

gator-agent

PR Review Status

Validation: This remains project-valid, concentrated numeric-safety work across existing CLI, TUI, server, Docker, and Podman paths.
Head SHA: 04139ebeb6c91f7ef3b69b9fc384bb8d57f2e8ea

Thanks @andrewwhitecdw. I checked the latest commit against each prior finding. The Docker memory boundary and Podman health-check bound are resolved, and the Docker/Podman floating-point guards now reject equality. However, two new tests do not exercise the equality values they claim, and the earlier Docker millicore finding remains unresolved at crates/openshell-driver-docker/src/lib.rs:2678: saturating_mul(1_000_000) still silently maps oversized input to i64::MAX. Replace it with checked multiplication, return the existing “too large” error, and test 9223372036855m as rejected while 9223372036854m remains accepted.

Four additional line-specific findings are included inline. No Fern docs update is needed for these arithmetic-only changes. E2E is not required; focused boundary unit coverage is appropriate.

Next state: gator:in-review

Comment thread crates/openshell-cli/src/run.rs Outdated
)
.into_diagnostic()?;
now_ms - dur_ms
now_ms.saturating_sub(dur_ms)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

gator-agent

Warning: saturating_sub does not clamp a duration older than the Unix epoch to zero; it returns a negative timestamp whenever the subtraction remains representable. Negative durations are also accepted and produce future timestamps. Reject negative durations, then clamp an overlong duration to zero (for example, now_ms.checked_sub(dur_ms).unwrap_or(0).max(0)) and add regression tests for both cases.

fn parse_cpu_limit_rejects_i64_max_boundary() {
// 9_223_372_037 cores * 1e9 rounds to 2^63, which used to pass the > check
// and silently saturate to i64::MAX. It must now be rejected.
let err = parse_cpu_limit("9223372037").unwrap_err();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

gator-agent

Warning: This does not test the equality boundary: 9_223_372_037 * 1e9 is already above 2^63, so changing the guard back from >= to > would still pass. Test 9223372036.854776, whose floating-point product rounds exactly to 2^63.

// 184_467_440_737_095_520 cores * 100_000 rounds to 2^64, which used
// to pass the > check and silently saturate to u64::MAX. It must now
// be rejected.
assert_eq!(parse_cpu_to_microseconds("184467440737095520"), None);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

gator-agent

Warning: This input is approximately 1,000 times the 2^64 boundary, contrary to the comment, so it does not protect the >= regression. Use 184467440737095.51616; multiplying that parsed value by 100_000 rounds to exactly 2^64.

let now_ms = current_time_ms();
let expires_at_ms = if state.config.ssh_session_ttl_secs > 0 {
now_ms + (state.config.ssh_session_ttl_secs as i64 * 1000)
let ttl_ms = i64::try_from(state.config.ssh_session_ttl_secs)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

gator-agent

Warning (CWE-20/CWE-613): Saturating an unrepresentable TTL to i64::MAX silently turns invalid configuration into a practically non-expiring SSH credential. Validate the TTL with checked conversion, multiplication, and addition, return an error when it cannot be represented, and add a boundary test.

@johntmyers johntmyers added gator:blocked Gator is blocked by process or repository gates and removed gator:in-review Gator is reviewing or awaiting PR review feedback labels Jul 29, 2026
@johntmyers

Copy link
Copy Markdown
Collaborator

/ok to test 04139eb

@andrewwhitecdw

Copy link
Copy Markdown
Contributor Author

/ok to test 314cb46

@johntmyers

Copy link
Copy Markdown
Collaborator

gator-agent

Blocked

Head SHA: 314cb466b52d46c47cd1bd9e5f2736d705571626

Thanks @andrewwhitecdw. The latest commit says it addresses the prior numeric-boundary findings, including the Docker millicore overflow, exact floating-point boundary tests, Podman interval validation, negative CLI durations, and SSH-session TTL validation.

Gator is blocked because GitHub currently reports this PR as conflicting with main (mergeable_state: dirty). I confirmed DCO is green, but the new head cannot enter independent code review or pipeline/E2E gating while the merge conflict remains.

Next action: please rebase on or merge the latest main, resolve the conflicts, and push the resolved head. Gator will independently review that new head, then decide and apply any required E2E label and /ok to test authorization.

The SSH session expiry calculation cast a u64 TTL to i64 and
multiplied by 1000 without bounds checking. A configured TTL larger
than i64::MAX / 1000 overflows and panics in debug builds.

Use i64::try_from with saturating_mul and saturating_add so any
out-of-range TTL clamps to i64::MAX milliseconds.

Signed-off-by: Andrew White <andrewh@cdw.com>
The Podman health-check interval multiplied a u64 seconds value by
1_000_000_000 to produce nanoseconds. A configured interval above
u64::MAX / 1e9 overflows. Use saturating_mul so huge values clamp to
u64::MAX nanoseconds instead of panicking.

Signed-off-by: Andrew White <andrewh@cdw.com>
parse_duration_to_ms parsed the numeric part as i64 and multiplied it
by a fixed multiplier, which can overflow for large user inputs such
as '100000000000000h'. Use checked_mul and return a clear error
instead of panicking. Add a regression test.

Signed-off-by: Andrew White <andrewh@cdw.com>
After parsing a duration, the CLI subtracted it from the current Unix
epoch milliseconds. A duration larger than now_ms underflows. Use
saturating_sub so the start timestamp clamps to 0 instead of
panicking in debug builds.

Signed-off-by: Andrew White <andrewh@cdw.com>
format_age subtracted created_secs from now before checking whether
the timestamp was in the future, so a future or skewed timestamp
underflowed before the guard could return '-'. Check created_secs >
now before subtracting, and add regression tests for future,
zero, and negative timestamps.

Signed-off-by: Andrew White <andrewh@cdw.com>
parse_cpu_to_microseconds checked that the input cores value was
finite, but the product cores * 100_000 could still overflow to
infinity (e.g. '1e300'). An infinite product cast to u64 saturates to
u64::MAX, producing a bogus quota. Reject the value when the product
is non-finite or exceeds u64::MAX. Add a regression test.

Signed-off-by: Andrew White <andrewh@cdw.com>
parse_cpu_limit and parse_memory_limit parsed user-supplied f64
values and multiplied them by large constants before casting to i64.
A huge input could make the product infinite, silently saturating to
i64::MAX. Check that the rounded product is finite and within i64
range before casting, and return a clear failed-precondition error.
Add regression tests.

Signed-off-by: Andrew White <andrewh@cdw.com>
Addresses gator-agent review feedback on NVIDIA#2462:

- Docker parse_cpu_limit / parse_memory_limit: use >= i64::MAX as f64 so inputs rounding to 2^63 are rejected instead of saturating.

- Podman parse_cpu_to_microseconds: use >= u64::MAX as f64 so inputs rounding to 2^64 are rejected instead of saturating.

- Podman health-check interval: replace saturating_mul with checked_mul and reject values exceeding i64::MAX nanoseconds.

- Add regression tests for the i64::MAX, u64::MAX, and health-check interval boundaries.

Signed-off-by: Andrew White <andrewh@cdw.com>
- Docker CPU millicores: use checked multiplication and reject overflow.
- Docker CPU/memory f64 boundaries: use exact equality tests (9223372036.854776
  cores, 8192Pi memory) and add millicore boundary test.
- Podman CPU: correct boundary test value to 184467440737095.51616 for exact
  2^64 microseconds.
- Podman health-check interval: pre-validate against i64::MAX / 1_000_000_000
  seconds and use checked multiplication; add boundary acceptance test.
- CLI duration parsing: reject negative durations; clamp overlong --since
  durations to zero instead of producing future timestamps.
- Server SSH TTL: use checked conversion, multiplication, and addition, and
  return InvalidArgument instead of silently creating non-expiring sessions.
@andrewwhitecdw
andrewwhitecdw force-pushed the fix-assorted-arithmetic-overflows/aw branch from 314cb46 to 1789522 Compare July 29, 2026 13:59
@andrewwhitecdw

Copy link
Copy Markdown
Contributor Author

/ok to test 1789522

Rebased onto the latest upstream main and resolved the merge conflict in crates/openshell-cli/src/commands/common.rs (kept the upstream multi-byte unit tests and merged in the overflow/negative-duration guards). The PR should now be mergeable; please re-run gator/E2E gating when ready.

@johntmyers johntmyers left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

gator-agent

PR Review Status

Validation: This remains project-valid, concentrated numeric-safety work across existing CLI, TUI, server, Docker, and Podman paths.
Head SHA: 1789522df14313e5062aacd46c322590850dd088

Thanks @andrewwhitecdw. I checked the rebased head against the earlier boundary findings. The exact 2^63/2^64 floating-point guards, Docker millicore overflow, Podman signed health-check interval limit, negative and overlong CLI durations, and checked SSH TTL arithmetic are resolved. The independent review found no blocking correctness or security issue. Two non-blocking regression-test hardening notes are included inline.

Docs: No Fern update is needed; these are representation-bound safety fixes rather than new user-facing product constraints.

E2E: test:e2e is required because this head changes Docker/Podman driver configuration behavior and server sandbox-session behavior.

Next state: gator:watch-pipeline

// Negative durations are rejected by parse_duration_to_ms. Overlong
// durations are clamped to zero (show all logs since the epoch) rather
// than silently producing a future timestamp.
now_ms.checked_sub(dur_ms).unwrap_or(0).max(0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

gator-agent

Non-blocking test hardening: The overlong-range fix has no direct regression test. Please consider testing dur_ms > now_ms and asserting the result is 0; extracting this arithmetic into a small pure helper would make the boundary easy to cover without exercising sandbox_logs.

Arc::get_mut(&mut state)
.expect("fresh test state is uniquely held")
.config
.ssh_session_ttl_secs = u64::MAX;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

gator-agent

Non-blocking test hardening: u64::MAX exercises the u64i64 conversion failure, but not the new checked_mul or checked_add paths. Please consider cases around (i64::MAX as u64 / 1000) + 1 and i64::MAX as u64 / 1000, or a helper accepting now_ms, to protect the exact largest-accepted and first-rejected TTL boundaries.

@johntmyers johntmyers added gator:watch-pipeline Gator is monitoring PR CI/CD status test:e2e Requires end-to-end coverage and removed gator:blocked Gator is blocked by process or repository gates labels Jul 29, 2026
@github-actions

Copy link
Copy Markdown

Label test:e2e applied, but pull-request/2462 is at 04139eb while the PR head is 1789522. A maintainer needs to comment /ok to test 1789522df14313e5062aacd46c322590850dd088 to refresh the mirror. Once the mirror catches up, re-run Branch E2E Checks from the Actions tab.

@johntmyers

Copy link
Copy Markdown
Collaborator

/ok to test 1789522

@johntmyers johntmyers added gator:in-review Gator is reviewing or awaiting PR review feedback gator:blocked Gator is blocked by process or repository gates and removed gator:watch-pipeline Gator is monitoring PR CI/CD status gator:in-review Gator is reviewing or awaiting PR review feedback labels Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gator:blocked Gator is blocked by process or repository gates test:e2e Requires end-to-end coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants