Skip to content

bug: expression JSON deserialization throws an uncaught exception on a non-string "type"/"term" #979

Description

@LuciferYang

Summary

The expression JSON deserialization functions in src/iceberg/expression/json_serde.cc call json[kType].get<std::string>() (and one json[kTerm].get<std::string>()) guarded only by is_object() and contains(kType). When "type" or "term" is a number, bool, null, or array, nlohmann throws json::type_error.302. These functions return Result<...> and the expression parse chain has no try/catch, so the exception escapes the Result contract and terminates any caller that lacks an exception barrier.

Root Cause

The unguarded get<std::string>() calls sit behind only is_object() && contains(kType): IsTransformTerm, NamedReferenceFromJson (both the type and the term node), both LiteralFromJson overloads' wrapper check, and ExpressionFromJson. The sibling OperationTypeFromJson already checks is_string() first, and util/json_util_internal.h exists precisely to convert nlohmann throws into JsonParseError. The unguarded sites are the outliers, not the policy.

Impact

These parsers run on real deserialization paths, all through ICEBERG_ASSIGN_OR_RAISE, which forwards a Result error but not a thrown exception. Table-metadata parsing is the live path today: FieldFromJson (src/iceberg/json_serde.cc) parses a field's initial-default / write-default through the type-aware LiteralFromJson, so a metadata file whose default is wrapped as {"type": <non-string>, "value": ...} throws instead of returning a parse error — the same path #877/#878 hardened. REST catalog responses reach the expression sites too: the scan-metrics report filter (metrics/json_serde.cc) and the residual, partition, and plan filters (catalog/rest/json_serde.cc) all parse server-supplied JSON. A non-string discriminator on any of these throws type_error.302 out of a Result-returning function and past the ICEBERG_ASSIGN_OR_RAISE call site, which is the same uncaught-exception-escaping-Result class as the merged #857.

Per SECURITY-THREAT-MODEL.md, catalog-supplied metadata is trusted input, so this is a robustness and contract issue, not a security one.

Proposed Fix

Add an is_string() check to each guard so a non-string type/term returns JsonParseError, mirroring OperationTypeFromJson.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions