Skip to content

fix(parser): a float literal beyond f64's range in a persistent rule stops the engine from starting #397

Closed

Description

@jsam

Found by the iql_statement fuzz target from #301 (local pre-campaign, 3 crash inputs, one root cause).

Problem

The IQL parsers accept float literals that are not finite numbers:

  • literals beyond f64's range, such as 1e400 or -1.0e309, parse to ±infinity;
  • Rust's own float spellings inf, infinity and NaN (any case) parse as floats, because the parsers call str::parse::<f64>() before they check for identifiers.

A persistent rule holding one is stored as JSON in the write-ahead log and the rule catalog. JSON has no infinity or NaN, so serde_json writes null. On the next start, StorageEngine::new fails to decode that record, and the engine does not start:

+r(1)
+p(X) <- r(Y), X = Y + 1e400      -> Rule 'p' registered.
(restart)
WalUnreadable { file: ".../persist/wal/current.wal", offset: 98,
  reason: "invalid type: null, expected f64 at line 1 column 379" }

Any client that may register a rule (editor and above) can stop the engine from starting with one statement. This is the same class as the persisted-rule depth trap fixed in #295.

The same literals also reach facts (+r(1e400), +r(inf)), vector literals ([1e400], [NaN]), comparisons and type refinements. Those do not break a restart, but they put infinities and NaN into data that no client asked for.

Where

src/parser/mod.rs (parse_term, the negative-literal path, vector literals, parse_arithmetic constants), src/statement/parser.rs (parse_single_term, vector literals) and src/statement/types.rs (refinement arguments) all use parse::<f64>() directly. Only parse_term's first check tested is_finite(), and it then fell through to the other paths.

Fix

Parse every float literal through one function that accepts only decimal syntax (digits, a fraction, an exponent) and refuses a literal beyond f64's range with a validation error. inf, infinity and NaN are then identifiers like any other. Underflow to zero (1e-400) stays accepted.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions