Skip to content

icaptcha-client: interactive_prompt hangs on non-interactive stdin, and the solvers overflow i64 on service-supplied prompts #345

Description

@beardthelion

Two small defects in the same crate, both reachable from the iCaptcha origin, which the crate's own
header treats as untrusted. Filing together since they share a fix surface and a test file.

1. interactive_prompt blocks forever on open-but-silent stdin

The docstring at lib.rs:353-355 states the contract: "Returns None when stdin isn't a usable
interactive source (e.g. an agent), so the caller surfaces a clear 'couldn't auto-solve' error
instead." The implementation does not honor it. lib.rs:366-368 is a bare read with no TTY test and
no timeout:

match stdin().read_line(&mut line) { Ok(0) | Err(_) => None,

Ok(0) is EOF only. An open pipe with a live writer and no data returns neither, so it hangs. There is
no TTY facility anywhere in the workspace: grep -rn "is-terminal\|atty\|IsTerminal" over crates/
returns nothing.

Measured, three stdin shapes against the read step compiled verbatim:

stdin = /dev/null (EOF)        -> returned None, exit 0
stdin = open pipe, no data     -> exit 124 (timed out, blocked)
stdin = pipe with data         -> returned Some("42"), exit 0

/dev/null, the typical CI shape, is fine. The bad case is an agent, wrapper script, or supervisor
handing the child an inherited pipe it keeps open. Under gl this also pins a tokio blocking thread
(crates/gl/src/http.rs:200-202).

Reachable because nothing enforces that the service's reply is one of SOLVABLE_TYPES (lib.rs:179):
an unexpected type, or an unparseable prompt inside a known one, makes solvers::solve return None
and drops straight to this branch (lib.rs:265-267), with solver: None on the production path.

Fix: check for a TTY before prompting, and return None when there is not one, which is what the
docstring already promises.

2. i64 overflow in the deterministic solvers

Plain i64 arithmetic with no checked_/saturating_/wrapping_ anywhere in solvers.rs:
:32-34 (acc += n, acc -= n), :131-136 (n[1] - n[0], last + d), :180-183 (isqrt_exact,
where cand * cand overflows for v near i64::MAX), plus :48, :53, :83, :104, :107, and
:159 (v.abs() at i64::MIN).

The workspace release profile (Cargo.toml:59-61) sets lto and strip but not overflow-checks, so
release wraps and debug panics. Both halves confirmed by running a standalone copy both ways:

DEBUG    PANIC  arithmetic  "What is 9223372036854775807 + 1?"
DEBUG    PANIC  sequence    "... 1, 4, 9223372036854775807, ?"
RELEASE  OK     arithmetic  -> Some("-9223372036854775808")
RELEASE  OK     algebra     -> Some("0")

Every panicking input is well-formed for the parser: each literal is in range and parses fine, and the
overflow happens during evaluation, so no malformed-input guard stands in the way. Source is
challenge.prompt (lib.rs:234), reaching solvers::solve at lib.rs:265.

Consequences are mild. In release a wrapped answer just fails grading; in debug the panic happens
inside spawn_blocking and gl reports "iCaptcha solver task panicked". Worth fixing because it is
cheap and the function's contract is already to return None when a prompt cannot be handled
(solvers.rs:10-11), so overflow should join that path via checked_*.

Not filed: the PoW iteration cap

For the record, since it came up in the same pass and I checked it: pow::solve burning its full
iteration cap on an unsolvable difficulty is not worth an issue. The bound already exists
(pow.rs:31, MAX_ITERS = 1 << 26) with a comment explaining the choice, and an early exit at an
"impossible" threshold buys nothing, since a difficulty of 28 is equally unsolvable in practice and
sits below any such threshold. Measured worst case is 121s of one core in a release build, which is
bounded, client-side, and self-inflicted. The module header's "well under a second" framing is
optimistic and could use a correcting comment; that is the whole of it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    crate:glgl — the contributor CLIkind:bugDefect fix — wrong or unsafe behaviorsev:lowCosmetic, cleanup, or nice-to-havesubsystem:identityDID/UCAN, http-sig auth, push authorization

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions