You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fory.apache.org by ch...@apache.org on 2026/08/04 04:03:47 UTC
(fory-site) 03/09: docs: consolidate object serialization security
This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch update_docs
in repository https://gitbox.apache.org/repos/asf/fory-site.git
commit 89fbe7fbb4bf0a6c81c4172a3e59299c54c74f8f
Author: chaokunyang <sh...@gmail.com>
AuthorDate: Tue Aug 4 10:52:12 2026 +0800
docs: consolidate object serialization security
---
.../deserialization-security-model.md | 712 ---------------------
docs/object-serialization/index.md | 16 +-
docs/object-serialization/runtimes.md | 38 --
docs/object-serialization/security.md | 704 +++++++++++++++++++-
.../deserialization-security-model.md | 532 ---------------
.../current/object-serialization/index.md | 16 +-
.../current/object-serialization/runtimes.md | 37 --
.../current/object-serialization/security.md | 524 ++++++++++++++-
8 files changed, 1246 insertions(+), 1333 deletions(-)
diff --git a/docs/object-serialization/deserialization-security-model.md b/docs/object-serialization/deserialization-security-model.md
deleted file mode 100644
index 11ba637b17..0000000000
--- a/docs/object-serialization/deserialization-security-model.md
+++ /dev/null
@@ -1,712 +0,0 @@
----
-title: Deserialization Security Model
-sidebar_position: 99
-id: deserialization-security-model
-license: |
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
----
-
-This document defines the security model for Apache Fory deserialization. It is
-a public security reference for classifying deserialization behavior and
-deciding where validation is required. It is not a vulnerability disclosure,
-does not describe exploit techniques, and does not document implementation
-history.
-
-The model is intentionally narrow. Fory should prevent resource and policy
-failures caused by untrusted input, but it should not add hot-path validation
-that only enforces byte-form strictness when doing so does not protect a Fory
-security boundary.
-
-## Scope
-
-This model applies only to deserializing Fory binary object-serialization data
-from untrusted or partially trusted sources. Fory JSON has a separate
-[security guide](../json/security.md) because it uses different readers,
-policies, codecs, and resource-accounting rules.
-
-It does not treat the semantic content of a successfully deserialized value as a
-Fory security boundary. A sender can always construct protocol-valid data whose
-value is chosen by that sender. Application authorization, object-level business
-rules, and domain-specific validation remain application responsibilities.
-The selected business invariant remains an application policy rather than a
-Fory protocol security boundary.
-
-This model does not govern memory-format paths unless a runtime explicitly
-exposes such a path through an untrusted deserialization API.
-
-## Trust Boundaries
-
-Fory deserialization should treat the encoded input as untrusted at API
-boundaries that accept external bytes or streams.
-
-Fory security boundaries include:
-
-- Resource ownership, such as memory, CPU progress, stream buffering, file
- handles, native allocations, callbacks, and retained read-side tables.
-- Runtime safety, such as avoiding crashes, panics, undefined behavior, and
- out-of-bounds reads or writes.
-- Explicit Fory policy checks, such as type, function, method, class, or
- registration policies that are intended to restrict what may be materialized.
-- Cleanup boundaries, where state created during a failed read must be released
- or reset before the next root operation.
-
-Fory security boundaries do not include:
-
-- The business meaning of a protocol-valid value.
-- Which protocol-allowed byte form was used for a value.
-- Whether a map, set, object, or metadata value uses one specific encoding
- shape, unless rejecting other shapes is an explicit owner policy or protects
- one of the boundaries above.
-
-## Type And Class Policy
-
-Type, class, function, method, registration, and deserialization policies are
-security boundaries when they are intended to restrict what untrusted bytes may
-materialize.
-
-For untrusted data, a bypass is security-relevant when encoded bytes can
-materialize a type, function, method, class, or dynamic object that the active
-Fory policy should reject. This includes bypasses of class or type
-registration, allow-list checkers, strict-mode checks, or language-specific
-deserialization policies.
-
-An application explicitly trusts a class when it registers that class or
-registers a serializer for that class. Both operations are configuration-time
-trust decisions under the class-registration policy. Explicitly selecting a
-static root serializer or static root target at the deserialization call is
-also an application authorization decision for that root path. Authorization
-of that statically selected root does not depend on a separate registration
-lookup; any registration needed to access registered identity or
-registration-backed metadata remains access-driven.
-
-Explicitly declaring or selecting a static field codec is itself an application
-authorization decision for that field; the codec does not need to be registered
-separately for authorization. Registering an enclosing class or schema also
-authorizes the statically declared field codecs and serializers that belong to
-that registered owner. This applies equally to declared Array, Set, Map, Struct,
-and other statically composed field paths. Those declared field paths do not
-require independent registration merely because their bodies are decoded
-without another type lookup. Likewise, an encoded declared-type marker does not
-create a registration bypass when it can only invoke the codec already selected
-by the authorized root or enclosing schema.
-
-These static authorization paths do not authorize an arbitrary alternative
-chosen by encoded type metadata. A dynamic or polymorphic type selected by
-input must still pass the active registration and deserialization-policy checks
-for that type. A serializer that Fory merely discovers or generates, and that
-is not reached through an explicitly selected static root or a registered
-enclosing owner, is serialization mechanics only and does not by itself
-authorize a dynamically selected class.
-
-Disabling registration or dynamic-type checks for trusted data is a caller
-configuration choice. That choice only removes the arbitrary-type materialization
-claim provided by that policy; it does not remove Fory's runtime-safety,
-resource, cleanup, retained-state, or no-progress-loop requirements for
-untrusted deserialization paths.
-
-Fory is not a sandbox for application-owned types. If a registered type or
-serializer is allowed by the active policy, the application owns whether that
-type's construction, hooks, setters, finalizers, or other logic is safe for the
-application's trust boundary.
-
-When policy-approved construction or callable execution is allowed, resource
-accounting should not claim to bound arbitrary code outside Fory's ownership.
-Fory-owned accounting can cover only objects and storage that Fory itself
-clearly creates or copies and that remain reachable from the materialized graph.
-Temporary helper allocations and user-code internals remain outside that
-accounting boundary.
-
-## Depth And Progress
-
-Deserialization paths that recurse through objects, metadata, containers, or
-references should enforce the runtime's configured depth limit before crafted
-nesting can exhaust the call stack or bypass cleanup. A malformed input that
-exceeds the configured depth should fail the root operation instead of
-continuing unbounded recursion.
-
-Loops that consume encoded data should guarantee byte progress, logical
-progress, or a terminal error. Inputs that can keep a reader in a no-progress
-loop are security-relevant even when they do not allocate memory.
-
-## Security Invariants
-
-Deserialization code must prevent the following outcomes for untrusted input:
-
-- Crash, panic, undefined behavior, or out-of-bounds memory access.
-- OOM or disproportionate allocation compared with bytes that are already
- supplied or proven readable.
-- No-progress loops, including loops where neither logical progress nor byte
- progress is guaranteed after malformed input.
-- Stream-buffer growth to an attacker-declared size before the corresponding
- bytes have been read or skipped exactly.
-- Resource leaks, including native allocations, handles, callbacks, or
- registered cleanup work that cannot run.
-- Retained attacker-controlled state after failure when that state can affect a
- later root operation or grow across operations.
-- Successful bypass of an explicit Fory policy boundary.
-
-When a path cannot produce one of these outcomes, earlier rejection of malformed
-bytes is normally a correctness or interoperability choice, not a security
-requirement.
-
-## Robustness Scope Gate
-
-Before reporting or fixing a deserialization robustness finding, establish a
-concrete consequence in the current implementation:
-
-- Crash, panic, undefined behavior, or out-of-bounds access.
-- Disproportionate allocation, CPU work, or stream growth.
-- A no-progress loop.
-- Persistent state, reference-table, or cache pollution.
-- Later-root corruption or a failed-root cleanup leak.
-- A concrete type, registration, callable, or deserialization-policy violation.
-
-Protocol strictness alone is outside this gate. Do not change code merely
-because a malformed or noncanonical flag, enum value, marker, length form, or
-reserved value is accepted, rejected late, decoded differently, or produces a
-less precise error. Such validation is actionable only when it prevents one of
-the concrete consequences above or implements an explicit public contract.
-
-## Controlled Deserialization Errors
-
-When a decoder determines that input is invalid for the active owner path, the
-root operation must return an error and run its normal failure cleanup. This is
-an outcome requirement, not an error-taxonomy requirement.
-
-Unless a public API or specification explicitly promises otherwise, Fory does
-not require a particular exception type, error code, message, detection layer,
-input offset, or earliest possible detection point. An existing bounded
-downstream buffer-underflow, type, reference, depth, or serializer error is a
-valid rejection. A decoder does not need a new local check merely to replace
-that controlled failure with a more specific or more uniform error.
-
-Tests for malformed input should prove that the root operation fails, cleanup
-remains correct, and any relevant security invariant is preserved. They should
-not pin an exact error type or message when doing so would require additional
-successful-path validation that protects no security boundary.
-
-## Non-Security Semantics
-
-The following patterns are not vulnerabilities by default:
-
-- Protocol-allowed collection chunking, map chunking, and field ordering.
-- Duplicate keys, set elements, or compatible fields that collapse according to
- the target data structure or owning serializer semantics.
-- Malformed ref, null, or type flags that eventually produce a read error.
-- Malformed scalar bytes that are consumed linearly and eventually produce a
- read error.
-- Reading an encoded body before later shape validation when the operation
- ultimately returns an error and does not create a security-invariant failure.
-- Materializing an array whose component is an interface already allowed as a
- class token. Allocating the reference array does not instantiate or execute
- the interface, and every non-null element must still pass the active policy
- for its concrete type. Treat this as security-relevant only if the array path
- bypasses that concrete element check, invokes a policy-forbidden callback, or
- violates a runtime-safety or resource invariant owned by Fory.
-
-Fory may still reject malformed forms for specification strictness or
-interoperability. That validation should be added only when it is required by
-the protocol owner, is effectively free on the relevant path, or protects a
-security invariant listed above. Do not add protocol-layer validation solely to
-reject scalar byte forms whose only effect is extra decode cost.
-
-### Value-bearing ref flags
-
-Some read paths intentionally share handling for multiple value-bearing flags.
-For example, when both `NotNullValue` and `RefValue` mean that an encoded value
-follows, a reader may merge their hot-path handling. This is not a malformed
-flag bug by itself. Treat it as a bug only if the merged handling loses required
-reference semantics, returns success across an explicit owner policy, or creates
-a resource or runtime-safety failure.
-
-## Allocation And Byte Availability
-
-Fory should not make large allocations from attacker-declared lengths before
-the required bytes are available or have been read exactly.
-
-For buffer-backed input:
-
-- Fixed-size binary values and primitive dense arrays should call the byte
- owner's readability check for the required encoded byte size before allocating
- the destination. For buffer-backed input this is normally a remaining-byte
- comparison.
-- Multi-byte element arrays should compute the required byte size with overflow
- checks before allocation.
-- Container readers that allocate backing storage or size-hint from a declared
- logical element count should call the byte owner's readability check for that
- count before that backing allocation or capacity reservation. This is not a
- full container-body validation; it is the allocation proof that the sender has
- supplied at least proportional input bytes before the reader preallocates from
- the count. Estimated memory-budget accounting may reserve budget before this
- byte check because it does not allocate backing storage.
-- Readers should not add count-based readability checks merely because a loop
- will read that many values when the destination grows incrementally and each
- item read still uses the normal byte-owner checks. The security boundary is
- direct preallocation from an untrusted count, not the existence of a counted
- loop.
-
-For stream-backed input:
-
-- Reading or skipping a large byte region is the proof that the bytes exist.
-- Byte-counted variable-length result allocation should use the byte owner's
- readability check before allocation. Skip paths may use bounded skip without
- materializing the skipped value.
-- A stream-backed buffer may hold the full requested encoded body after that
- body has been read from the stream. It must not reserve the attacker-declared
- length before input bytes prove that length exists.
-- Stream-backed fill buffers should grow geometrically from the current proven
- buffer size, such as by doubling current capacity. Growth must not be capped
- to the immediate fill target: for small fills the target is barely above the
- current capacity, so cap-to-target degenerates into constant-size growth
- steps that copy the whole buffer on every small read and make stream
- deserialization O(n^2) overall. A byte owner may use an owner-local
- availability signal as a one-shot growth hint when the stream implementation
- itself is caller-owned trusted code, and may then reserve the full immediate
- target at once while keeping at least the geometric growth step; if that hint
- is absent or insufficient, the reader must fall back to bounded geometric
- growth from already buffered bytes. Serializers should not add their own
- availability branches.
-- A truncated stream should fail before allocating the final deserialized value
- and should allocate only for bytes actually read plus bounded spare capacity.
-
-The byte owner should stay byte-oriented. Buffer, reader, or read-context APIs
-may expose byte read and byte skip operations, but string decoding, decimal
-parsing, primitive-array encoding, compression modes, and collection capacity
-policy belong to the owning serializers.
-
-## Collection And Map Capacity
-
-Large valid collection inputs are allowed. If the input contains many encoded
-elements, proportional deserialization is expected.
-
-The security requirement is to avoid disproportionate preallocation from a
-declared logical count before enough input bytes justify that capacity. When
-the repeated element or entry body is proven to consume at least one byte, a
-reader that allocates or reserves from the declared count should call
-`checkReadableBytes(logicalCount)` or the runtime equivalent before that
-allocation. When the body may consume no bytes, the readable-byte requirement
-may exclude the root operation's remaining unbacked-container allowance. The
-reader must still account for actual input progress while reading the
-container. The byte check does not decode the whole container, validate element
-semantics, or replace chunk validation. Readers that do not preallocate from
-the logical count may still grow proportionally as elements are actually read.
-
-Map or collection chunk validation is security-relevant only when missing
-validation can cause a no-progress loop, unbounded resource growth, retained
-state, or success across a Fory policy boundary. Protocol-allowed chunk
-segmentation is normal input and is not a security issue by itself.
-
-## Unbacked Container Work Budget
-
-Runtimes enforce a root-scoped limit on count-driven collection elements and
-map entries whose repeated read bodies are not backed by input progress. The
-public option is named `maxUnbackedContainerItems` or the language-equivalent
-spelling. Its default is `8192`; values must be non-negative, and zero is a
-strict limit rather than an unlimited sentinel.
-
-The allowance is shared by all nested collections, maps, and compatible field
-skip operations in one root read. Collection readers account for completed
-items every 1024 elements and at the final partial window. Map readers account
-at existing protocol chunk boundaries. Bytes actually consumed by the repeated
-item bodies offset the completed item count in the same window. The budget does
-not add framing, reject values on write, change reference publication, or
-replace graph-memory accounting.
-
-Readers whose exact repeated operation is known to consume at least one byte
-retain their direct loop and proportional readable-byte check. Generated and
-compiled serializers should remove budget access and periodic branches from
-those proven-positive paths.
-
-## Graph Memory Budget
-
-Runtimes should enforce a per-operation approximate gate for estimated memory created by one
-materialized graph. This is cumulative accounting for graph owners created by one top-level
-deserialization operation; it is not exact heap measurement and it is not a raw element-slot limit.
-Actual process memory can be higher than the configured gate.
-
-The public configuration is `maxGraphMemoryBytes`. The default is a fixed `128 MiB` for all input
-forms; positive user configuration overrides the default. Explicit non-positive configuration is
-invalid and should be rejected when the runtime is created. The budget is not derived from input
-size, and stream budgeting should not depend on dynamic bytes-read accounting.
-
-Graph budget accounting should:
-
-- be initialized in top-level read state, with cleanup owned by the top-level deserialization
- `finally`;
-- account only for Fory-created objects or storage that are retained by the
- returned value graph; temporary helper objects used only during construction
- are outside the graph budget;
-- not claim to budget arbitrary constructor, callable, descriptor, finalizer,
- or state-restoration internals that run after an explicit policy allows that
- code;
-- keep read context/read state limited to raw byte reservation; counted arithmetic and collection,
- map, array, struct, and object storage formulas belong in the concrete serializer or generated
- serializer owner;
-- reject arithmetic overflow before comparing budget or allocating;
-- estimate lower-bound shallow owner storage: reference-backed or heap-materialized collections,
- maps, sets, and reference arrays reserve nonzero shallow self cost plus
- backing/reference/inline storage, and reference-backed or heap-materialized struct, record,
- POJO, tuple/product, compatible, generated, and dynamic object owners reserve a nonzero shallow
- self cost plus shallow field storage;
-- use a 4-byte reference slot when the actual reference slot size is not cheap or reliable to query,
- and use primitive/value field widths for inline storage;
-- preserve existing byte-availability checks before backing allocation or capacity reservation;
-- skip enum/union as separate owners and skip dedicated string, binary, primitive scalar, primitive
- array, and primitive dense-array leaf owners unless a runtime-specific owner section explicitly
- includes them.
-
-Skipped leaf owners must still be gated by remaining input bytes. If the unread input does not
-contain enough bytes for a string, binary value, primitive scalar, primitive array, or primitive
-dense array, the runtime must not read or create that leaf value.
-
-Each runtime must inspect the concrete owner path before choosing formulas. Reserve self storage
-exactly once at the owner that stores, boxes, or allocates the value. Deserialization facades may
-reset the budget for each operation, but must not pre-reserve the top-level result type, self bytes,
-or value storage.
-Reference-backed paths reserve parent owner self cost plus reference storage, while each referenced
-heap owner reserves its own shallow self cost when materialized. Inline/value paths reserve inline
-element, field, or boxed storage in the holder/allocation owner; top-level value serializers and
-generated struct/product read paths must not charge their own self storage.
-For inline/value collection or map runtimes, the top-level value container itself is not charged by
-the deserialization facade or by the container serializer only because it is the returned value.
-Nested value containers are charged as inline slots of the parent holder or as backing storage
-elements of the outer collection that actually owns those slots. Pointer, box, smart-pointer, or
-type-erased materialization paths reserve the shallow storage for the heap value they allocate.
-Parents must not recursively include child object, collection, map, string, binary, or primitive
-dense-array contents; the child owner reserves its own shallow memory when it is materialized.
-
-### Java Fory Core
-
-Java Fory core primitive-array serializers reserve the portable array header plus the logical
-length multiplied by the primitive storage width. Primitive-list serializers reserve the returned
-list's shallow owner, the backing-array header, and the same primitive storage. These known-length
-paths reserve once after their existing proportional readable-byte check and before allocation;
-they do not use incremental batches. Compressed inputs use the decompressed logical length, while
-temporary compressed arrays remain construction scratch outside the retained graph budget.
-Float16 and BFloat16 dense-array carriers also include their wrapper's shallow owner. When a boxed
-list conversion first decodes a primitive array, the array's reservation remains as credit toward
-the final list estimate, and the conversion reserves only a positive remaining difference.
-
-### Generated Structural Targets
-
-Wire members and physical storage are separate inputs. Properties, accessors, interfaces, and
-logical schema aliases are not physical fields and must not be charged as storage. A field that is
-both serialized and stored is counted once. A storage-only declaration contributes its field width
-but must not enter wire metadata or generated reads and writes.
-
-For C# ordinary classes, each directly annotated class owns the physical instance fields declared
-by that class. An inheritable class provider publishes the cumulative parent-provider value plus
-those direct fields. A sealed concrete serializer uses the same cumulative expression privately.
-A concrete descendant uses the immediate accessible provider value and its own direct fields; it
-must not enumerate referenced private metadata or reconstruct parent storage. The concrete object
-serializer reserves one shallow object owner plus this cumulative field storage.
-
-A C# external class declaration owns the exact third-party physical fields it lists. An exact
-field mapping contributes storage. A visible property mapping does not, so its backing field must
-be listed separately. An ignored mapping must identify one exact class field and is storage-only.
-External struct declarations support visible member mappings only.
-Discoverable unmapped public instance fields may be added once. A `BaseOnly` declaration can own
-the complete target and target-ancestor prefix used by an ordinary child. It must list every
-non-public physical field in that prefix; the generator does not scan the referenced assembly for
-private layout.
-
-Exact external private identities are version-pinned package ABI assertions. Runtime wire access
-uses exact accessors and must not fall back to reflection, layout probing, or a different member.
-Storage-only private declarations have no runtime accessor, so the application must validate them
-against the pinned package version.
-
-Dart generators may additionally include public instance fields visible on the target at
-generation time. Swift macros cannot inspect another type's stored layout and therefore use only
-the external declaration. In every runtime, these formulas are resolved during generation and
-must not add reflection, layout probing, allocation, or field enumeration to deserialization hot
-paths. The normal owner rules still apply: a reference target reserves its shallow owner and field
-storage, while an inline value target is charged by the holder that owns its storage.
-
-### Runtime-Specific Owner Notes
-
-#### C++
-
-C++ plain structs, products, and standard-library containers are value storage unless a pointer,
-smart pointer, or type-erased owner allocates them on the heap. Top-level deserialization initializes
-the remaining graph budget but does not reserve `sizeof(T)` for the returned value. Plain value
-serializers must not reserve their own `sizeof(T)` only because they are reading a value.
-
-Generic collection and map serializers reserve the lower-bound element, key, and value storage
-owned by the container path. Nested value container headers are charged when they are inline slots
-of a parent object or elements in an outer container backing store. Smart-pointer and type-erased
-materialization paths reserve the shallow storage for the heap value they allocate before publishing
-or returning it. Generic C++ paths must not invent standard-library header, node, bucket, allocator,
-or debug-layout overheads.
-
-#### Rust
-
-Rust structs, tuples, enums, and collection values are inline value storage unless a `Box`, `Rc`,
-`Arc`, or type-erased owner allocates them. Top-level and derived value read paths initialize or
-consume the budget but do not reserve `size_of::<Self>()` for the value being read. `Vec`, `HashMap`,
-`BTreeMap`, and similar serializers reserve backing or entry value storage that they allocate from
-counts; nested value container headers are charged as parent inline fields or outer backing elements.
-
-Boxed, reference-counted, and type-erased materialization paths reserve `size_of::<T>()` for the heap
-payload they create. Compile-time `size_of::<T>()` formulas are acceptable in those allocation
-owners, but value serializers should not add a parallel self-reserve for the same `T`.
-
-Before count-derived allocation, Rust owners whose exact repeated operation is proven to consume at
-least one byte retain the full readable-byte gate. Uncertain owners require readable bytes only for
-the portion of the count not covered by the remaining unbacked-item allowance. Apply the selected
-gate exactly once at the allocation owner; do not repeat it after reading shared metadata. Writers
-continue to encode legal compact or empty bodies and do not enforce this reader-side allowance.
-
-Fixed arrays do not allocate from their validated wire count and omit the allocation gate. `Vec`,
-`VecDeque`, and `BinaryHeap` also omit it for zero-sized elements because they create no
-count-derived backing allocation in that case. Node, bucket, and entry owners retain the gate where
-the declared count drives allocation. Implementations must not substitute guessed allocation costs,
-padding bytes, a global compact-body bypass, or a second collection or map codec.
-
-#### Swift
-
-Swift structs, enums, tuples, and collection values are value storage. Top-level value reads and
-nested value serializers should not reserve their own self storage. The holder that owns the value,
-such as a struct field, array backing store, dictionary entry storage, or boxed/dynamic
-materialization path, owns the corresponding graph-budget reservation.
-
-Array, dictionary, and set serializers may reserve lower-bound backing storage using stable Swift
-type-size information, such as `MemoryLayout<T>.stride`, when they allocate or reserve that storage.
-Class, existential, or boxed materialization paths reserve owner storage when Fory creates the
-retained object or box. Runtime object-layout probing should not be added to hot read paths.
-
-#### Go
-
-Go structs and slice or map headers are value storage unless a pointer, interface materialization, or
-other heap owner allocates them. Top-level deserialization and struct value serializers should not
-reserve the returned struct or a nested inline struct by themselves. Pointer serializers reserve the
-concrete struct storage when they allocate a retained `*T`.
-
-Slice, array, map, and set serializers reserve the backing or entry storage they allocate from
-declared counts. Element and entry widths should come from stable type information captured by the
-serializer or resolver when possible; read loops should not recompute reflective size information
-when the owner already knows the concrete type. Interface or dynamic paths reserve only storage that
-Fory clearly materializes and retains.
-
-#### C\#
-
-C# combines reference owners and inline value types. Classes, arrays, lists, dictionaries, hash sets,
-and other heap containers reserve a nonzero shallow owner cost plus direct backing, reference-slot,
-or inline element storage. A dictionary is a reference-type container even when its key or value type
-is a struct, so the dictionary owner is still charged separately from its entry storage.
-
-Value structs do not reserve their own self storage when read inline; the holder that stores the
-struct, such as an object field, array element, list backing store, dictionary entry, box, or dynamic
-materialization path, owns that reservation. Boxing, `object`, and dynamic materialization paths
-reserve a boxed owner when Fory creates the retained box. Owner constants should be real portable
-lower bounds for the relevant C# object or container shape, not placeholder markers.
-
-Runtimes should not guess object headers, array headers, allocator headers, debug-mode fields, hash
-buckets, tree links, hash-chain links, node headers, map-entry objects, spare blocks, or runtime
-table layouts unless the owner path has a cheap, stable, explicit lower-bound storage signal and
-documents the formula. Owner constants should be real lower bounds for the owner shape, not
-placeholder markers.
-
-## Skip Semantics
-
-Skipping unknown or incompatible data is classified by concrete impact, not by
-whether the runtime materializes a temporary value.
-
-Directly consuming encoded contents is useful when it is simple and owned by the
-current runtime path. It is not a security requirement for complex fields such
-as lists, sets, and maps. A runtime may materialize a value and discard it when
-that preserves the existing serializer ownership model.
-
-For extension, dynamic, or user-owned types, the owning runtime may not always
-have enough information to skip without invoking a registered serializer. In
-that case, classify the behavior by concrete impact:
-
-- Resource leak, retained state, no-progress loop, or policy bypass is
- security-relevant.
-- Bounded materialization followed by an error or discard is allowed unless it
- creates meaningful memory or CPU pressure.
-- Pure strictness about whether a skipped value used one specific encoding shape
- is not a security issue.
-
-## Metadata And Type Resolution
-
-Metadata parsing is security-sensitive when it affects retained read-side state,
-type dispatch, or policy decisions.
-
-Metadata readers should:
-
-- Avoid unbounded recursion in nested metadata structures.
-- Avoid unbounded table growth from attacker-controlled metadata streams.
-- Validate metadata bodies before using them to bypass or replace existing
- policy decisions.
-- For Java metadata paths, keep name-level checks such as `TypeChecker` and the
- disallowed-class list before `Class.forName` by routing remote class-name
- loading through the existing `TypeResolver.loadClass` owner. Do not bypass
- that owner with direct class loading from TypeDef or TypeMeta names. A rejected
- input name must not cause class loading. Preserve registration, dynamic-loading,
- and unknown-type semantics while moving this decision before loading. Checks
- that require a materialized `Class<?>` remain after loading; do not replace
- them with string-only approximations.
-- Pass a complete input array descriptor to `TypeChecker`. Input may derive up
- to six array dimensions from an accepted component class. Higher-dimensional
- arrays require an exact trusted full-array registration or checked name-cache
- entry so input cannot make the JVM derive an unbounded family of array classes.
-- Reset or release metadata state at the correct root-operation boundary.
-
-A class-resolution cache reachable from untrusted deserialization may publish
-an entry only from explicit trusted configuration or after the active class
-policy has accepted the resolved class. A cache hit therefore represents an
-already trusted and validated `Class<?>` and should use that cached class
-without repeating class loading or name-level `TypeChecker` work. Only a cache
-miss performs those name-level checks and publishes the accepted result.
-Checks that require the materialized `Class<?>` remain owned by their existing
-caller. A cache entry that stores a data-only unknown-class placeholder may
-return that same placeholder on an exact hit, but must not authorize loading the
-original missed wire name.
-Exact registered-name-table hits are trusted for both ID and name registrations,
-and exact checked name-cache hits are trusted. After both exact lookups miss, a
-reader must not infer another accepted name from inverse registration,
-class-keyed state, or `Class.getName()`. A custom-name registration does not by
-itself publish the Java class name as an additional alias; ID registration does
-publish the Java class name.
-
-Remote metadata that can create persistent read state must be bounded before
-that state is retained. The check is resource control only: it must not change
-wire compatibility, type registration, dynamic class loading, unknown-type
-handling, deserialization policy, or schema-evolution semantics. Failed or
-incompatible metadata must not consume schema-version limits, and metadata
-cache hits or generated field readers must not add validation, hashing,
-allocation, or policy work for these limits. The concrete sequence for metadata
-parsing, cache publishing, exact-local matching, and counting belongs to the
-[xlang implementation guide](../specification/xlang_implementation_guide.md).
-
-The checked metadata cache is the only owner of whether a received TypeDef or
-TypeMeta header has already been validated. A metadata cache hit means the
-header was previously parsed, body/hash-validated, policy-checked, and
-published by the owning cache, so the reader must skip the remaining metadata
-body and use the cached metadata without repeating body validation, hash
-validation, limit checks, exact-local checks, or policy work. A metadata cache
-miss is the only path that parses the metadata body, validates its hash and
-shape, enforces metadata limits, performs exact-local byte comparison, and
-publishes to the cache. Do not add separate nullable flags, sentinel headers,
-per-TypeInfo acceptance markers, or parallel state to represent this decision.
-
-Only metadata that is actually carried as a TypeDef or TypeMeta body is subject
-to metadata body and schema-version limits. Compatible named enum, ext, and
-union metadata normally has one version, but still counts against remote
-metadata total limits when it is sent as shared metadata. Pure id-based enum,
-ext, and typed-union values use type id plus user type id and must not be moved
-onto this metadata body path.
-
-Remote metadata bodies and struct field lists must also be bounded on the cold
-metadata parse path. `maxTypeMetaBytes` limits the encoded metadata body bytes
-for one received TypeDef or TypeMeta body, excluding the 8-byte header and any
-extended-size varint. `maxTypeFields` limits the number of fields declared by
-one received struct metadata body. For Java native TypeDef class layers, the
-field limit applies to the total field count across the class layers in that
-one TypeDef. These limits are checked before copying, decompressing, reserving,
-or allocating from attacker-declared metadata sizes or field counts.
-
-The default limits are `maxTypeFields = 512` and `maxTypeMetaBytes = 4096`.
-Runtimes should report limit failures as possible malicious data and tell users
-to increase the exact option only when the data is not malicious. These limits
-must not introduce validation on metadata cache-hit, generated serializer, or
-already-resolved type-id hot paths.
-
-Metadata byte-form strictness alone is not a security requirement. Rejecting a
-metadata shape is useful only when the owner wants that strictness or when the
-shape changes type identity, retained state, resource use, or policy behavior.
-
-## Reference Tracking
-
-Reference tracking is part of the wire protocol and is performance-sensitive.
-Readers may use sentinel values and shared value-bearing branches to keep hot
-paths compact.
-
-Reference tracking validation is security-relevant when malformed input can:
-
-- Access an out-of-range reference without reporting an error.
-- Leave retained reference state after a failed root operation.
-- Register unbounded callbacks or resolver state before the referenced value is
- available.
-- Cause a no-progress loop or crash.
-
-Reference tracking validation is not required merely because a malformed flag is
-not rejected at the earliest possible byte. Lazy rejection is acceptable when
-the root operation still returns an error and no security invariant is violated.
-The downstream error does not need to be a dedicated reference-protocol error.
-
-## Error Propagation And Cleanup
-
-Fory runtimes may intentionally use lazy error propagation. After a read records
-an error, later read steps may continue until the outer operation observes and
-returns the error.
-
-This is acceptable when the continued work cannot:
-
-- Crash or panic.
-- Allocate or retain attacker-controlled state.
-- Leak resources.
-- Bypass required cleanup.
-- Return success across an explicit validation or policy boundary.
-
-Nested `try`/`finally` or equivalent cleanup should be added only when the
-outer root-operation cleanup cannot cover the state or resource owned by the
-nested path.
-
-## Performance Requirements
-
-Security validation must preserve Fory hot-path performance. Do not add
-validation solely for strictness when it introduces:
-
-- Per-element object allocation.
-- Dynamic dispatch or callbacks in hot loops.
-- Wrapper objects or result carriers on success paths.
-- Extra copying for buffer-backed string, binary, or primitive-array reads.
-- Branches that do not protect a security invariant.
-- Helper calls or generated-code expansion whose only purpose is to normalize
- an eventual error's type, message, location, or timing.
-
-Prefer owner-local checks that can be inlined and that already use information
-available in the current serializer. Do not move serializer-owned semantics into
-generic read-context helpers.
-
-## Classification Guide
-
-Use the following questions when reviewing deserialization behavior:
-
-1. Can this input crash, panic, or access memory out of bounds?
-2. Can a small or unproven input length cause disproportionate allocation?
-3. Can a stream-backed reader grow a buffer before exact read or skip proves the
- bytes exist?
-4. Can a loop continue without byte progress or logical progress?
-5. Can the path retain attacker-controlled state after the root operation fails?
-6. Can the path leak resources or skip required cleanup?
-7. Can the path return success across an explicit Fory policy boundary?
-8. Is the proposed validation effectively free in the relevant hot path?
-
-If the answer to the first seven questions is no, the issue is normally not a
-security finding. If the validation is not effectively free, avoid adding it
-unless the protocol owner explicitly requires it.
-
-## Documentation Boundaries
-
-Security model documents must not include exploit samples, CVE narratives,
-line-level vulnerability candidates, branch history, migration timelines, or
-cleanup plans. Keep those details in private reports, issues, or pull requests
-as appropriate.
-
-Public security documentation should describe durable boundaries and invariants,
-not the history of how the implementation reached them.
diff --git a/docs/object-serialization/index.md b/docs/object-serialization/index.md
index 996a5080dd..9ed38d9f8f 100644
--- a/docs/object-serialization/index.md
+++ b/docs/object-serialization/index.md
@@ -38,11 +38,23 @@ representation, and Fory JSON is a Java JSON codec; use the
Choose a runtime to find its installation route, lifecycle, exact APIs, configuration, type
registration, schema behavior, extensions, platforms, and troubleshooting:
-[Browse supported runtimes](runtimes.md)
+| Runtime | Modes | Documentation |
+| --------------------- | -------------------- | ------------------------------------------------------ |
+| Java | xlang and native | [Java runtime](./java/index.md) |
+| Python | xlang and native | [Python runtime](./python/index.md) |
+| C++ | xlang and native | [C++ runtime](./cpp/index.md) |
+| Go | xlang and native | [Go runtime](./go/index.md) |
+| Rust | xlang and native | [Rust runtime](./rust/index.md) |
+| JavaScript/TypeScript | xlang | [JavaScript/TypeScript runtime](./javascript/index.md) |
+| C# | xlang | [C# runtime](./csharp/index.md) |
+| Swift | xlang | [Swift runtime](./swift/index.md) |
+| Dart | xlang | [Dart runtime](./dart/index.md) |
+| Scala | xlang and JVM native | [Scala runtime](./scala/index.md) |
+| Kotlin | xlang and JVM native | [Kotlin runtime](./kotlin/index.md) |
## Security
-Before decoding externally supplied bytes, read [Object Serialization Security](security.md). It
+Before decoding externally supplied bytes, read [Security](security.md). It
covers accepted-type policy, registration, resource limits, transport responsibilities, and
negative verification for both modes.
diff --git a/docs/object-serialization/runtimes.md b/docs/object-serialization/runtimes.md
deleted file mode 100644
index 491608e543..0000000000
--- a/docs/object-serialization/runtimes.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-title: Object Serialization Runtimes
-sidebar_position: 3
-id: runtimes
-license: |
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
----
-
-Choose the runtime whose API your application uses. Runtime documentation is scoped to
-binary object serialization; Row Format, Fory JSON, compiler, and gRPC pages remain with
-their respective capability guides.
-
-| Runtime | Modes | Documentation |
-| --------------------- | -------------------- | ------------------------------------------------------ |
-| Java | xlang and native | [Java runtime](./java/index.md) |
-| Python | xlang and native | [Python runtime](./python/index.md) |
-| C++ | xlang and native | [C++ runtime](./cpp/index.md) |
-| Go | xlang and native | [Go runtime](./go/index.md) |
-| Rust | xlang and native | [Rust runtime](./rust/index.md) |
-| JavaScript/TypeScript | xlang | [JavaScript/TypeScript runtime](./javascript/index.md) |
-| C# | xlang | [C# runtime](./csharp/index.md) |
-| Swift | xlang | [Swift runtime](./swift/index.md) |
-| Dart | xlang | [Dart runtime](./dart/index.md) |
-| Scala | xlang and JVM native | [Scala runtime](./scala/index.md) |
-| Kotlin | xlang and JVM native | [Kotlin runtime](./kotlin/index.md) |
diff --git a/docs/object-serialization/security.md b/docs/object-serialization/security.md
index 28789dc466..09e88a25a8 100644
--- a/docs/object-serialization/security.md
+++ b/docs/object-serialization/security.md
@@ -1,6 +1,6 @@
---
-title: Object Serialization Security
-sidebar_position: 4
+title: Security
+sidebar_position: 99
id: security
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
@@ -19,7 +19,7 @@ license: |
limitations under the License.
---
-This guide defines the trust boundary and safe operating model for Fory binary object serialization in xlang and native mode. Contributor-facing classification rules live in the [deserialization security model](deserialization-security-model.md).
+This guide defines the trust boundary and safe operating model for Fory binary object serialization in xlang and native mode. It also provides the contributor-facing classification rules used to review untrusted deserialization paths.
Fory is an in-process serialization library. Applications link Fory into their
own process, configure serializers and type policies, and call Fory APIs to
@@ -61,7 +61,7 @@ equivalent allow-list policy is the relevant gate. Generated serializer code is
derived from checked type descriptors rather than from attacker-controlled byte
contents.
-The [deserialization security model](deserialization-security-model.md) defines how to
+The [security review model](#security-review-model) defines how to
classify these boundaries for untrusted deserialization paths.
## Non-Goals
@@ -100,7 +100,7 @@ Disabling registration or using dynamic deserialization on trusted data is a
configuration choice. For untrusted data, bypassing an explicit Fory policy,
crashing, leaking resources, retaining attacker-controlled state, or allocating
disproportionately remains security-relevant as described in the
-[deserialization security model](deserialization-security-model.md).
+[security review model](#security-review-model).
## Resource Limits
@@ -208,3 +208,697 @@ reader rejects:
Also verify the application's external authentication, integrity, request-size,
timeout, and domain-validation controls independently of Fory.
+
+## Security Review Model
+
+This document defines the security model for Apache Fory deserialization. It is
+a public security reference for classifying deserialization behavior and
+deciding where validation is required. It is not a vulnerability disclosure,
+does not describe exploit techniques, and does not document implementation
+history.
+
+The model is intentionally narrow. Fory should prevent resource and policy
+failures caused by untrusted input, but it should not add hot-path validation
+that only enforces byte-form strictness when doing so does not protect a Fory
+security boundary.
+
+### Scope
+
+This model applies only to deserializing Fory binary object-serialization data
+from untrusted or partially trusted sources. Fory JSON has a separate
+[security guide](../json/security.md) because it uses different readers,
+policies, codecs, and resource-accounting rules.
+
+It does not treat the semantic content of a successfully deserialized value as a
+Fory security boundary. A sender can always construct protocol-valid data whose
+value is chosen by that sender. Application authorization, object-level business
+rules, and domain-specific validation remain application responsibilities.
+The selected business invariant remains an application policy rather than a
+Fory protocol security boundary.
+
+This model does not govern memory-format paths unless a runtime explicitly
+exposes such a path through an untrusted deserialization API.
+
+### Trust Boundaries
+
+Fory deserialization should treat the encoded input as untrusted at API
+boundaries that accept external bytes or streams.
+
+Fory security boundaries include:
+
+- Resource ownership, such as memory, CPU progress, stream buffering, file
+ handles, native allocations, callbacks, and retained read-side tables.
+- Runtime safety, such as avoiding crashes, panics, undefined behavior, and
+ out-of-bounds reads or writes.
+- Explicit Fory policy checks, such as type, function, method, class, or
+ registration policies that are intended to restrict what may be materialized.
+- Cleanup boundaries, where state created during a failed read must be released
+ or reset before the next root operation.
+
+Fory security boundaries do not include:
+
+- The business meaning of a protocol-valid value.
+- Which protocol-allowed byte form was used for a value.
+- Whether a map, set, object, or metadata value uses one specific encoding
+ shape, unless rejecting other shapes is an explicit owner policy or protects
+ one of the boundaries above.
+
+### Type And Class Policy
+
+Type, class, function, method, registration, and deserialization policies are
+security boundaries when they are intended to restrict what untrusted bytes may
+materialize.
+
+For untrusted data, a bypass is security-relevant when encoded bytes can
+materialize a type, function, method, class, or dynamic object that the active
+Fory policy should reject. This includes bypasses of class or type
+registration, allow-list checkers, strict-mode checks, or language-specific
+deserialization policies.
+
+An application explicitly trusts a class when it registers that class or
+registers a serializer for that class. Both operations are configuration-time
+trust decisions under the class-registration policy. Explicitly selecting a
+static root serializer or static root target at the deserialization call is
+also an application authorization decision for that root path. Authorization
+of that statically selected root does not depend on a separate registration
+lookup; any registration needed to access registered identity or
+registration-backed metadata remains access-driven.
+
+Explicitly declaring or selecting a static field codec is itself an application
+authorization decision for that field; the codec does not need to be registered
+separately for authorization. Registering an enclosing class or schema also
+authorizes the statically declared field codecs and serializers that belong to
+that registered owner. This applies equally to declared Array, Set, Map, Struct,
+and other statically composed field paths. Those declared field paths do not
+require independent registration merely because their bodies are decoded
+without another type lookup. Likewise, an encoded declared-type marker does not
+create a registration bypass when it can only invoke the codec already selected
+by the authorized root or enclosing schema.
+
+These static authorization paths do not authorize an arbitrary alternative
+chosen by encoded type metadata. A dynamic or polymorphic type selected by
+input must still pass the active registration and deserialization-policy checks
+for that type. A serializer that Fory merely discovers or generates, and that
+is not reached through an explicitly selected static root or a registered
+enclosing owner, is serialization mechanics only and does not by itself
+authorize a dynamically selected class.
+
+Disabling registration or dynamic-type checks for trusted data is a caller
+configuration choice. That choice only removes the arbitrary-type materialization
+claim provided by that policy; it does not remove Fory's runtime-safety,
+resource, cleanup, retained-state, or no-progress-loop requirements for
+untrusted deserialization paths.
+
+Fory is not a sandbox for application-owned types. If a registered type or
+serializer is allowed by the active policy, the application owns whether that
+type's construction, hooks, setters, finalizers, or other logic is safe for the
+application's trust boundary.
+
+When policy-approved construction or callable execution is allowed, resource
+accounting should not claim to bound arbitrary code outside Fory's ownership.
+Fory-owned accounting can cover only objects and storage that Fory itself
+clearly creates or copies and that remain reachable from the materialized graph.
+Temporary helper allocations and user-code internals remain outside that
+accounting boundary.
+
+### Depth And Progress
+
+Deserialization paths that recurse through objects, metadata, containers, or
+references should enforce the runtime's configured depth limit before crafted
+nesting can exhaust the call stack or bypass cleanup. A malformed input that
+exceeds the configured depth should fail the root operation instead of
+continuing unbounded recursion.
+
+Loops that consume encoded data should guarantee byte progress, logical
+progress, or a terminal error. Inputs that can keep a reader in a no-progress
+loop are security-relevant even when they do not allocate memory.
+
+### Security Invariants
+
+Deserialization code must prevent the following outcomes for untrusted input:
+
+- Crash, panic, undefined behavior, or out-of-bounds memory access.
+- OOM or disproportionate allocation compared with bytes that are already
+ supplied or proven readable.
+- No-progress loops, including loops where neither logical progress nor byte
+ progress is guaranteed after malformed input.
+- Stream-buffer growth to an attacker-declared size before the corresponding
+ bytes have been read or skipped exactly.
+- Resource leaks, including native allocations, handles, callbacks, or
+ registered cleanup work that cannot run.
+- Retained attacker-controlled state after failure when that state can affect a
+ later root operation or grow across operations.
+- Successful bypass of an explicit Fory policy boundary.
+
+When a path cannot produce one of these outcomes, earlier rejection of malformed
+bytes is normally a correctness or interoperability choice, not a security
+requirement.
+
+### Robustness Scope Gate
+
+Before reporting or fixing a deserialization robustness finding, establish a
+concrete consequence in the current implementation:
+
+- Crash, panic, undefined behavior, or out-of-bounds access.
+- Disproportionate allocation, CPU work, or stream growth.
+- A no-progress loop.
+- Persistent state, reference-table, or cache pollution.
+- Later-root corruption or a failed-root cleanup leak.
+- A concrete type, registration, callable, or deserialization-policy violation.
+
+Protocol strictness alone is outside this gate. Do not change code merely
+because a malformed or noncanonical flag, enum value, marker, length form, or
+reserved value is accepted, rejected late, decoded differently, or produces a
+less precise error. Such validation is actionable only when it prevents one of
+the concrete consequences above or implements an explicit public contract.
+
+### Controlled Deserialization Errors
+
+When a decoder determines that input is invalid for the active owner path, the
+root operation must return an error and run its normal failure cleanup. This is
+an outcome requirement, not an error-taxonomy requirement.
+
+Unless a public API or specification explicitly promises otherwise, Fory does
+not require a particular exception type, error code, message, detection layer,
+input offset, or earliest possible detection point. An existing bounded
+downstream buffer-underflow, type, reference, depth, or serializer error is a
+valid rejection. A decoder does not need a new local check merely to replace
+that controlled failure with a more specific or more uniform error.
+
+Tests for malformed input should prove that the root operation fails, cleanup
+remains correct, and any relevant security invariant is preserved. They should
+not pin an exact error type or message when doing so would require additional
+successful-path validation that protects no security boundary.
+
+### Non-Security Semantics
+
+The following patterns are not vulnerabilities by default:
+
+- Protocol-allowed collection chunking, map chunking, and field ordering.
+- Duplicate keys, set elements, or compatible fields that collapse according to
+ the target data structure or owning serializer semantics.
+- Malformed ref, null, or type flags that eventually produce a read error.
+- Malformed scalar bytes that are consumed linearly and eventually produce a
+ read error.
+- Reading an encoded body before later shape validation when the operation
+ ultimately returns an error and does not create a security-invariant failure.
+- Materializing an array whose component is an interface already allowed as a
+ class token. Allocating the reference array does not instantiate or execute
+ the interface, and every non-null element must still pass the active policy
+ for its concrete type. Treat this as security-relevant only if the array path
+ bypasses that concrete element check, invokes a policy-forbidden callback, or
+ violates a runtime-safety or resource invariant owned by Fory.
+
+Fory may still reject malformed forms for specification strictness or
+interoperability. That validation should be added only when it is required by
+the protocol owner, is effectively free on the relevant path, or protects a
+security invariant listed above. Do not add protocol-layer validation solely to
+reject scalar byte forms whose only effect is extra decode cost.
+
+#### Value-bearing ref flags
+
+Some read paths intentionally share handling for multiple value-bearing flags.
+For example, when both `NotNullValue` and `RefValue` mean that an encoded value
+follows, a reader may merge their hot-path handling. This is not a malformed
+flag bug by itself. Treat it as a bug only if the merged handling loses required
+reference semantics, returns success across an explicit owner policy, or creates
+a resource or runtime-safety failure.
+
+### Allocation And Byte Availability
+
+Fory should not make large allocations from attacker-declared lengths before
+the required bytes are available or have been read exactly.
+
+For buffer-backed input:
+
+- Fixed-size binary values and primitive dense arrays should call the byte
+ owner's readability check for the required encoded byte size before allocating
+ the destination. For buffer-backed input this is normally a remaining-byte
+ comparison.
+- Multi-byte element arrays should compute the required byte size with overflow
+ checks before allocation.
+- Container readers that allocate backing storage or size-hint from a declared
+ logical element count should call the byte owner's readability check for that
+ count before that backing allocation or capacity reservation. This is not a
+ full container-body validation; it is the allocation proof that the sender has
+ supplied at least proportional input bytes before the reader preallocates from
+ the count. Estimated memory-budget accounting may reserve budget before this
+ byte check because it does not allocate backing storage.
+- Readers should not add count-based readability checks merely because a loop
+ will read that many values when the destination grows incrementally and each
+ item read still uses the normal byte-owner checks. The security boundary is
+ direct preallocation from an untrusted count, not the existence of a counted
+ loop.
+
+For stream-backed input:
+
+- Reading or skipping a large byte region is the proof that the bytes exist.
+- Byte-counted variable-length result allocation should use the byte owner's
+ readability check before allocation. Skip paths may use bounded skip without
+ materializing the skipped value.
+- A stream-backed buffer may hold the full requested encoded body after that
+ body has been read from the stream. It must not reserve the attacker-declared
+ length before input bytes prove that length exists.
+- Stream-backed fill buffers should grow geometrically from the current proven
+ buffer size, such as by doubling current capacity. Growth must not be capped
+ to the immediate fill target: for small fills the target is barely above the
+ current capacity, so cap-to-target degenerates into constant-size growth
+ steps that copy the whole buffer on every small read and make stream
+ deserialization O(n^2) overall. A byte owner may use an owner-local
+ availability signal as a one-shot growth hint when the stream implementation
+ itself is caller-owned trusted code, and may then reserve the full immediate
+ target at once while keeping at least the geometric growth step; if that hint
+ is absent or insufficient, the reader must fall back to bounded geometric
+ growth from already buffered bytes. Serializers should not add their own
+ availability branches.
+- A truncated stream should fail before allocating the final deserialized value
+ and should allocate only for bytes actually read plus bounded spare capacity.
+
+The byte owner should stay byte-oriented. Buffer, reader, or read-context APIs
+may expose byte read and byte skip operations, but string decoding, decimal
+parsing, primitive-array encoding, compression modes, and collection capacity
+policy belong to the owning serializers.
+
+### Collection And Map Capacity
+
+Large valid collection inputs are allowed. If the input contains many encoded
+elements, proportional deserialization is expected.
+
+The security requirement is to avoid disproportionate preallocation from a
+declared logical count before enough input bytes justify that capacity. When
+the repeated element or entry body is proven to consume at least one byte, a
+reader that allocates or reserves from the declared count should call
+`checkReadableBytes(logicalCount)` or the runtime equivalent before that
+allocation. When the body may consume no bytes, the readable-byte requirement
+may exclude the root operation's remaining unbacked-container allowance. The
+reader must still account for actual input progress while reading the
+container. The byte check does not decode the whole container, validate element
+semantics, or replace chunk validation. Readers that do not preallocate from
+the logical count may still grow proportionally as elements are actually read.
+
+Map or collection chunk validation is security-relevant only when missing
+validation can cause a no-progress loop, unbounded resource growth, retained
+state, or success across a Fory policy boundary. Protocol-allowed chunk
+segmentation is normal input and is not a security issue by itself.
+
+### Unbacked Container Work Budget
+
+Runtimes enforce a root-scoped limit on count-driven collection elements and
+map entries whose repeated read bodies are not backed by input progress. The
+public option is named `maxUnbackedContainerItems` or the language-equivalent
+spelling. Its default is `8192`; values must be non-negative, and zero is a
+strict limit rather than an unlimited sentinel.
+
+The allowance is shared by all nested collections, maps, and compatible field
+skip operations in one root read. Collection readers account for completed
+items every 1024 elements and at the final partial window. Map readers account
+at existing protocol chunk boundaries. Bytes actually consumed by the repeated
+item bodies offset the completed item count in the same window. The budget does
+not add framing, reject values on write, change reference publication, or
+replace graph-memory accounting.
+
+Readers whose exact repeated operation is known to consume at least one byte
+retain their direct loop and proportional readable-byte check. Generated and
+compiled serializers should remove budget access and periodic branches from
+those proven-positive paths.
+
+### Graph Memory Budget
+
+Runtimes should enforce a per-operation approximate gate for estimated memory created by one
+materialized graph. This is cumulative accounting for graph owners created by one top-level
+deserialization operation; it is not exact heap measurement and it is not a raw element-slot limit.
+Actual process memory can be higher than the configured gate.
+
+The public configuration is `maxGraphMemoryBytes`. The default is a fixed `128 MiB` for all input
+forms; positive user configuration overrides the default. Explicit non-positive configuration is
+invalid and should be rejected when the runtime is created. The budget is not derived from input
+size, and stream budgeting should not depend on dynamic bytes-read accounting.
+
+Graph budget accounting should:
+
+- be initialized in top-level read state, with cleanup owned by the top-level deserialization
+ `finally`;
+- account only for Fory-created objects or storage that are retained by the
+ returned value graph; temporary helper objects used only during construction
+ are outside the graph budget;
+- not claim to budget arbitrary constructor, callable, descriptor, finalizer,
+ or state-restoration internals that run after an explicit policy allows that
+ code;
+- keep read context/read state limited to raw byte reservation; counted arithmetic and collection,
+ map, array, struct, and object storage formulas belong in the concrete serializer or generated
+ serializer owner;
+- reject arithmetic overflow before comparing budget or allocating;
+- estimate lower-bound shallow owner storage: reference-backed or heap-materialized collections,
+ maps, sets, and reference arrays reserve nonzero shallow self cost plus
+ backing/reference/inline storage, and reference-backed or heap-materialized struct, record,
+ POJO, tuple/product, compatible, generated, and dynamic object owners reserve a nonzero shallow
+ self cost plus shallow field storage;
+- use a 4-byte reference slot when the actual reference slot size is not cheap or reliable to query,
+ and use primitive/value field widths for inline storage;
+- preserve existing byte-availability checks before backing allocation or capacity reservation;
+- skip enum/union as separate owners and skip dedicated string, binary, primitive scalar, primitive
+ array, and primitive dense-array leaf owners unless a runtime-specific owner section explicitly
+ includes them.
+
+Skipped leaf owners must still be gated by remaining input bytes. If the unread input does not
+contain enough bytes for a string, binary value, primitive scalar, primitive array, or primitive
+dense array, the runtime must not read or create that leaf value.
+
+Each runtime must inspect the concrete owner path before choosing formulas. Reserve self storage
+exactly once at the owner that stores, boxes, or allocates the value. Deserialization facades may
+reset the budget for each operation, but must not pre-reserve the top-level result type, self bytes,
+or value storage.
+Reference-backed paths reserve parent owner self cost plus reference storage, while each referenced
+heap owner reserves its own shallow self cost when materialized. Inline/value paths reserve inline
+element, field, or boxed storage in the holder/allocation owner; top-level value serializers and
+generated struct/product read paths must not charge their own self storage.
+For inline/value collection or map runtimes, the top-level value container itself is not charged by
+the deserialization facade or by the container serializer only because it is the returned value.
+Nested value containers are charged as inline slots of the parent holder or as backing storage
+elements of the outer collection that actually owns those slots. Pointer, box, smart-pointer, or
+type-erased materialization paths reserve the shallow storage for the heap value they allocate.
+Parents must not recursively include child object, collection, map, string, binary, or primitive
+dense-array contents; the child owner reserves its own shallow memory when it is materialized.
+
+#### Java Fory Core
+
+Java Fory core primitive-array serializers reserve the portable array header plus the logical
+length multiplied by the primitive storage width. Primitive-list serializers reserve the returned
+list's shallow owner, the backing-array header, and the same primitive storage. These known-length
+paths reserve once after their existing proportional readable-byte check and before allocation;
+they do not use incremental batches. Compressed inputs use the decompressed logical length, while
+temporary compressed arrays remain construction scratch outside the retained graph budget.
+Float16 and BFloat16 dense-array carriers also include their wrapper's shallow owner. When a boxed
+list conversion first decodes a primitive array, the array's reservation remains as credit toward
+the final list estimate, and the conversion reserves only a positive remaining difference.
+
+#### Generated Structural Targets
+
+Wire members and physical storage are separate inputs. Properties, accessors, interfaces, and
+logical schema aliases are not physical fields and must not be charged as storage. A field that is
+both serialized and stored is counted once. A storage-only declaration contributes its field width
+but must not enter wire metadata or generated reads and writes.
+
+For C# ordinary classes, each directly annotated class owns the physical instance fields declared
+by that class. An inheritable class provider publishes the cumulative parent-provider value plus
+those direct fields. A sealed concrete serializer uses the same cumulative expression privately.
+A concrete descendant uses the immediate accessible provider value and its own direct fields; it
+must not enumerate referenced private metadata or reconstruct parent storage. The concrete object
+serializer reserves one shallow object owner plus this cumulative field storage.
+
+A C# external class declaration owns the exact third-party physical fields it lists. An exact
+field mapping contributes storage. A visible property mapping does not, so its backing field must
+be listed separately. An ignored mapping must identify one exact class field and is storage-only.
+External struct declarations support visible member mappings only.
+Discoverable unmapped public instance fields may be added once. A `BaseOnly` declaration can own
+the complete target and target-ancestor prefix used by an ordinary child. It must list every
+non-public physical field in that prefix; the generator does not scan the referenced assembly for
+private layout.
+
+Exact external private identities are version-pinned package ABI assertions. Runtime wire access
+uses exact accessors and must not fall back to reflection, layout probing, or a different member.
+Storage-only private declarations have no runtime accessor, so the application must validate them
+against the pinned package version.
+
+Dart generators may additionally include public instance fields visible on the target at
+generation time. Swift macros cannot inspect another type's stored layout and therefore use only
+the external declaration. In every runtime, these formulas are resolved during generation and
+must not add reflection, layout probing, allocation, or field enumeration to deserialization hot
+paths. The normal owner rules still apply: a reference target reserves its shallow owner and field
+storage, while an inline value target is charged by the holder that owns its storage.
+
+#### Runtime-Specific Owner Notes
+
+##### C++
+
+C++ plain structs, products, and standard-library containers are value storage unless a pointer,
+smart pointer, or type-erased owner allocates them on the heap. Top-level deserialization initializes
+the remaining graph budget but does not reserve `sizeof(T)` for the returned value. Plain value
+serializers must not reserve their own `sizeof(T)` only because they are reading a value.
+
+Generic collection and map serializers reserve the lower-bound element, key, and value storage
+owned by the container path. Nested value container headers are charged when they are inline slots
+of a parent object or elements in an outer container backing store. Smart-pointer and type-erased
+materialization paths reserve the shallow storage for the heap value they allocate before publishing
+or returning it. Generic C++ paths must not invent standard-library header, node, bucket, allocator,
+or debug-layout overheads.
+
+##### Rust
+
+Rust structs, tuples, enums, and collection values are inline value storage unless a `Box`, `Rc`,
+`Arc`, or type-erased owner allocates them. Top-level and derived value read paths initialize or
+consume the budget but do not reserve `size_of::<Self>()` for the value being read. `Vec`, `HashMap`,
+`BTreeMap`, and similar serializers reserve backing or entry value storage that they allocate from
+counts; nested value container headers are charged as parent inline fields or outer backing elements.
+
+Boxed, reference-counted, and type-erased materialization paths reserve `size_of::<T>()` for the heap
+payload they create. Compile-time `size_of::<T>()` formulas are acceptable in those allocation
+owners, but value serializers should not add a parallel self-reserve for the same `T`.
+
+Before count-derived allocation, Rust owners whose exact repeated operation is proven to consume at
+least one byte retain the full readable-byte gate. Uncertain owners require readable bytes only for
+the portion of the count not covered by the remaining unbacked-item allowance. Apply the selected
+gate exactly once at the allocation owner; do not repeat it after reading shared metadata. Writers
+continue to encode legal compact or empty bodies and do not enforce this reader-side allowance.
+
+Fixed arrays do not allocate from their validated wire count and omit the allocation gate. `Vec`,
+`VecDeque`, and `BinaryHeap` also omit it for zero-sized elements because they create no
+count-derived backing allocation in that case. Node, bucket, and entry owners retain the gate where
+the declared count drives allocation. Implementations must not substitute guessed allocation costs,
+padding bytes, a global compact-body bypass, or a second collection or map codec.
+
+##### Swift
+
+Swift structs, enums, tuples, and collection values are value storage. Top-level value reads and
+nested value serializers should not reserve their own self storage. The holder that owns the value,
+such as a struct field, array backing store, dictionary entry storage, or boxed/dynamic
+materialization path, owns the corresponding graph-budget reservation.
+
+Array, dictionary, and set serializers may reserve lower-bound backing storage using stable Swift
+type-size information, such as `MemoryLayout<T>.stride`, when they allocate or reserve that storage.
+Class, existential, or boxed materialization paths reserve owner storage when Fory creates the
+retained object or box. Runtime object-layout probing should not be added to hot read paths.
+
+##### Go
+
+Go structs and slice or map headers are value storage unless a pointer, interface materialization, or
+other heap owner allocates them. Top-level deserialization and struct value serializers should not
+reserve the returned struct or a nested inline struct by themselves. Pointer serializers reserve the
+concrete struct storage when they allocate a retained `*T`.
+
+Slice, array, map, and set serializers reserve the backing or entry storage they allocate from
+declared counts. Element and entry widths should come from stable type information captured by the
+serializer or resolver when possible; read loops should not recompute reflective size information
+when the owner already knows the concrete type. Interface or dynamic paths reserve only storage that
+Fory clearly materializes and retains.
+
+##### C\#
+
+C# combines reference owners and inline value types. Classes, arrays, lists, dictionaries, hash sets,
+and other heap containers reserve a nonzero shallow owner cost plus direct backing, reference-slot,
+or inline element storage. A dictionary is a reference-type container even when its key or value type
+is a struct, so the dictionary owner is still charged separately from its entry storage.
+
+Value structs do not reserve their own self storage when read inline; the holder that stores the
+struct, such as an object field, array element, list backing store, dictionary entry, box, or dynamic
+materialization path, owns that reservation. Boxing, `object`, and dynamic materialization paths
+reserve a boxed owner when Fory creates the retained box. Owner constants should be real portable
+lower bounds for the relevant C# object or container shape, not placeholder markers.
+
+Runtimes should not guess object headers, array headers, allocator headers, debug-mode fields, hash
+buckets, tree links, hash-chain links, node headers, map-entry objects, spare blocks, or runtime
+table layouts unless the owner path has a cheap, stable, explicit lower-bound storage signal and
+documents the formula. Owner constants should be real lower bounds for the owner shape, not
+placeholder markers.
+
+### Skip Semantics
+
+Skipping unknown or incompatible data is classified by concrete impact, not by
+whether the runtime materializes a temporary value.
+
+Directly consuming encoded contents is useful when it is simple and owned by the
+current runtime path. It is not a security requirement for complex fields such
+as lists, sets, and maps. A runtime may materialize a value and discard it when
+that preserves the existing serializer ownership model.
+
+For extension, dynamic, or user-owned types, the owning runtime may not always
+have enough information to skip without invoking a registered serializer. In
+that case, classify the behavior by concrete impact:
+
+- Resource leak, retained state, no-progress loop, or policy bypass is
+ security-relevant.
+- Bounded materialization followed by an error or discard is allowed unless it
+ creates meaningful memory or CPU pressure.
+- Pure strictness about whether a skipped value used one specific encoding shape
+ is not a security issue.
+
+### Metadata And Type Resolution
+
+Metadata parsing is security-sensitive when it affects retained read-side state,
+type dispatch, or policy decisions.
+
+Metadata readers should:
+
+- Avoid unbounded recursion in nested metadata structures.
+- Avoid unbounded table growth from attacker-controlled metadata streams.
+- Validate metadata bodies before using them to bypass or replace existing
+ policy decisions.
+- For Java metadata paths, keep name-level checks such as `TypeChecker` and the
+ disallowed-class list before `Class.forName` by routing remote class-name
+ loading through the existing `TypeResolver.loadClass` owner. Do not bypass
+ that owner with direct class loading from TypeDef or TypeMeta names. A rejected
+ input name must not cause class loading. Preserve registration, dynamic-loading,
+ and unknown-type semantics while moving this decision before loading. Checks
+ that require a materialized `Class<?>` remain after loading; do not replace
+ them with string-only approximations.
+- Pass a complete input array descriptor to `TypeChecker`. Input may derive up
+ to six array dimensions from an accepted component class. Higher-dimensional
+ arrays require an exact trusted full-array registration or checked name-cache
+ entry so input cannot make the JVM derive an unbounded family of array classes.
+- Reset or release metadata state at the correct root-operation boundary.
+
+A class-resolution cache reachable from untrusted deserialization may publish
+an entry only from explicit trusted configuration or after the active class
+policy has accepted the resolved class. A cache hit therefore represents an
+already trusted and validated `Class<?>` and should use that cached class
+without repeating class loading or name-level `TypeChecker` work. Only a cache
+miss performs those name-level checks and publishes the accepted result.
+Checks that require the materialized `Class<?>` remain owned by their existing
+caller. A cache entry that stores a data-only unknown-class placeholder may
+return that same placeholder on an exact hit, but must not authorize loading the
+original missed wire name.
+Exact registered-name-table hits are trusted for both ID and name registrations,
+and exact checked name-cache hits are trusted. After both exact lookups miss, a
+reader must not infer another accepted name from inverse registration,
+class-keyed state, or `Class.getName()`. A custom-name registration does not by
+itself publish the Java class name as an additional alias; ID registration does
+publish the Java class name.
+
+Remote metadata that can create persistent read state must be bounded before
+that state is retained. The check is resource control only: it must not change
+wire compatibility, type registration, dynamic class loading, unknown-type
+handling, deserialization policy, or schema-evolution semantics. Failed or
+incompatible metadata must not consume schema-version limits, and metadata
+cache hits or generated field readers must not add validation, hashing,
+allocation, or policy work for these limits. The concrete sequence for metadata
+parsing, cache publishing, exact-local matching, and counting belongs to the
+[xlang implementation guide](../specification/xlang_implementation_guide.md).
+
+The checked metadata cache is the only owner of whether a received TypeDef or
+TypeMeta header has already been validated. A metadata cache hit means the
+header was previously parsed, body/hash-validated, policy-checked, and
+published by the owning cache, so the reader must skip the remaining metadata
+body and use the cached metadata without repeating body validation, hash
+validation, limit checks, exact-local checks, or policy work. A metadata cache
+miss is the only path that parses the metadata body, validates its hash and
+shape, enforces metadata limits, performs exact-local byte comparison, and
+publishes to the cache. Do not add separate nullable flags, sentinel headers,
+per-TypeInfo acceptance markers, or parallel state to represent this decision.
+
+Only metadata that is actually carried as a TypeDef or TypeMeta body is subject
+to metadata body and schema-version limits. Compatible named enum, ext, and
+union metadata normally has one version, but still counts against remote
+metadata total limits when it is sent as shared metadata. Pure id-based enum,
+ext, and typed-union values use type id plus user type id and must not be moved
+onto this metadata body path.
+
+Remote metadata bodies and struct field lists must also be bounded on the cold
+metadata parse path. `maxTypeMetaBytes` limits the encoded metadata body bytes
+for one received TypeDef or TypeMeta body, excluding the 8-byte header and any
+extended-size varint. `maxTypeFields` limits the number of fields declared by
+one received struct metadata body. For Java native TypeDef class layers, the
+field limit applies to the total field count across the class layers in that
+one TypeDef. These limits are checked before copying, decompressing, reserving,
+or allocating from attacker-declared metadata sizes or field counts.
+
+The default limits are `maxTypeFields = 512` and `maxTypeMetaBytes = 4096`.
+Runtimes should report limit failures as possible malicious data and tell users
+to increase the exact option only when the data is not malicious. These limits
+must not introduce validation on metadata cache-hit, generated serializer, or
+already-resolved type-id hot paths.
+
+Metadata byte-form strictness alone is not a security requirement. Rejecting a
+metadata shape is useful only when the owner wants that strictness or when the
+shape changes type identity, retained state, resource use, or policy behavior.
+
+### Reference Tracking
+
+Reference tracking is part of the wire protocol and is performance-sensitive.
+Readers may use sentinel values and shared value-bearing branches to keep hot
+paths compact.
+
+Reference tracking validation is security-relevant when malformed input can:
+
+- Access an out-of-range reference without reporting an error.
+- Leave retained reference state after a failed root operation.
+- Register unbounded callbacks or resolver state before the referenced value is
+ available.
+- Cause a no-progress loop or crash.
+
+Reference tracking validation is not required merely because a malformed flag is
+not rejected at the earliest possible byte. Lazy rejection is acceptable when
+the root operation still returns an error and no security invariant is violated.
+The downstream error does not need to be a dedicated reference-protocol error.
+
+### Error Propagation And Cleanup
+
+Fory runtimes may intentionally use lazy error propagation. After a read records
+an error, later read steps may continue until the outer operation observes and
+returns the error.
+
+This is acceptable when the continued work cannot:
+
+- Crash or panic.
+- Allocate or retain attacker-controlled state.
+- Leak resources.
+- Bypass required cleanup.
+- Return success across an explicit validation or policy boundary.
+
+Nested `try`/`finally` or equivalent cleanup should be added only when the
+outer root-operation cleanup cannot cover the state or resource owned by the
+nested path.
+
+### Performance Requirements
+
+Security validation must preserve Fory hot-path performance. Do not add
+validation solely for strictness when it introduces:
+
+- Per-element object allocation.
+- Dynamic dispatch or callbacks in hot loops.
+- Wrapper objects or result carriers on success paths.
+- Extra copying for buffer-backed string, binary, or primitive-array reads.
+- Branches that do not protect a security invariant.
+- Helper calls or generated-code expansion whose only purpose is to normalize
+ an eventual error's type, message, location, or timing.
+
+Prefer owner-local checks that can be inlined and that already use information
+available in the current serializer. Do not move serializer-owned semantics into
+generic read-context helpers.
+
+### Classification Guide
+
+Use the following questions when reviewing deserialization behavior:
+
+1. Can this input crash, panic, or access memory out of bounds?
+2. Can a small or unproven input length cause disproportionate allocation?
+3. Can a stream-backed reader grow a buffer before exact read or skip proves the
+ bytes exist?
+4. Can a loop continue without byte progress or logical progress?
+5. Can the path retain attacker-controlled state after the root operation fails?
+6. Can the path leak resources or skip required cleanup?
+7. Can the path return success across an explicit Fory policy boundary?
+8. Is the proposed validation effectively free in the relevant hot path?
+
+If the answer to the first seven questions is no, the issue is normally not a
+security finding. If the validation is not effectively free, avoid adding it
+unless the protocol owner explicitly requires it.
+
+### Documentation Boundaries
+
+Security model documents must not include exploit samples, CVE narratives,
+line-level vulnerability candidates, branch history, migration timelines, or
+cleanup plans. Keep those details in private reports, issues, or pull requests
+as appropriate.
+
+Public security documentation should describe durable boundaries and invariants,
+not the history of how the implementation reached them.
diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/deserialization-security-model.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/deserialization-security-model.md
deleted file mode 100644
index 70b9a4e891..0000000000
--- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/deserialization-security-model.md
+++ /dev/null
@@ -1,532 +0,0 @@
----
-title: 反序列化安全模型
-sidebar_position: 99
-id: deserialization-security-model
-license: |
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
----
-
-本文档定义 Apache Fory 反序列化的安全模型。它是用于分类反序列化行为并确定何处需要
-验证的公开安全参考。它不是漏洞披露,不描述利用技术,也不记录实现历史。
-
-该模型有意保持较窄的范围。Fory 应防止不可信输入导致的资源和策略失效,但当验证无法
-保护 Fory 安全边界时,不应在热路径上添加只用于强制字节形式严格性的验证。
-
-## 范围
-
-该模型仅适用于反序列化来自不可信或部分可信来源的 Fory 二进制对象序列化数据。Fory
-JSON 使用不同的读取器、策略、codec 和资源核算规则,因此有单独的
-[安全指南](../json/security.md)。
-
-该模型不将成功反序列化值的语义内容视为 Fory 安全边界。发送方始终可以构造协议有效、
-但值由其自行选择的数据。应用授权、对象级业务规则和领域特有验证仍由应用负责。所选择
-的业务不变量仍属于应用策略,而不是 Fory 协议安全边界。
-
-除非某个运行时通过不可信反序列化 API 显式公开内存格式路径,否则该模型不管理此类路径。
-
-## 信任边界
-
-在接受外部字节或流的 API 边界上,Fory 反序列化应将编码输入视为不可信。
-
-Fory 安全边界包括:
-
-- 资源所有权,例如内存、CPU 进度、流缓冲、文件句柄、原生内存分配、回调和保留的
- 读取端表。
-- 运行时安全,例如避免崩溃、panic、未定义行为和越界读写。
-- 显式 Fory 策略检查,例如旨在限制可实体化内容的类型、函数、方法、类或注册策略。
-- 清理边界,即读取失败期间创建的状态必须在下一次根操作前释放或重置。
-
-Fory 安全边界不包括:
-
-- 协议有效值的业务含义。
-- 一个值使用了哪一种协议允许的字节形式。
-- map、set、对象或元数据值是否使用某一种特定编码形状,除非拒绝其他形状属于显式
- 所有者策略,或能够保护上述边界之一。
-
-## 类型与类策略
-
-当类型、类、函数、方法、注册和反序列化策略旨在限制不可信字节可以实体化的内容时,
-这些策略就是安全边界。
-
-对于不可信数据,如果编码字节能够实体化当前 Fory 策略本应拒绝的类型、函数、方法、类
-或动态对象,则绕过行为与安全相关。这包括绕过类或类型注册、白名单检查器、严格模式
-检查或语言特有的反序列化策略。
-
-应用注册一个类或为该类注册序列化器时,即显式信任该类。在类注册策略下,这两项操作
-都是配置时信任决策。在反序列化调用处显式选择静态根序列化器或静态根目标,也是应用
-对该根路径的授权决策。静态选择根的授权不依赖单独的注册查找;访问已注册身份或注册
-支持的元数据所需的任何注册,仍由实际访问驱动。
-
-显式声明或选择静态字段 codec 本身就是应用对该字段的授权决策;该 codec 无需为了授权
-而单独注册。注册外围类或 Schema 也会授权属于该已注册所有者的静态声明字段 codec 和
-序列化器。这同样适用于声明的 Array、Set、Map、Struct 及其他静态组合字段路径。这些
-已声明字段路径不会仅仅因为其主体无需再次查找类型即可解码,就需要独立注册。同样,
-如果编码的声明类型标记只能调用已由授权根或外围 Schema 选择的 codec,它也不会造成
-注册绕过。
-
-这些静态授权路径不会授权由编码类型元数据选择的任意替代项。输入选择的动态或多态类型
-仍必须通过该类型当前有效的注册和反序列化策略检查。Fory 仅仅发现或生成的序列化器,
-如果不是通过显式选择的静态根或已注册外围所有者到达,就只是序列化机制,本身不会授权
-动态选择的类。
-
-对可信数据禁用注册或动态类型检查属于调用方配置选择。该选择只会移除相应策略对任意
-类型实体化提供的保证;它不会移除 Fory 对不可信反序列化路径的运行时安全、资源、清理、
-保留状态或无进度循环要求。
-
-Fory 不是应用拥有类型的沙箱。如果当前策略允许某个已注册类型或序列化器,则该类型的
-构造、hook、setter、finalizer 或其他逻辑对于应用信任边界是否安全,由应用负责。
-
-允许经过策略批准的构造或可调用对象执行时,资源核算不应声称能够约束 Fory 所有权之外
-的任意代码。Fory 拥有的核算只能覆盖由 Fory 自身明确创建或复制,并且仍可从实体化对象图
-访问的对象和存储。临时辅助分配和用户代码内部资源仍在该核算边界之外。
-
-## 深度与进度
-
-通过对象、元数据、容器或引用进行递归的反序列化路径,应在恶意构造的嵌套耗尽调用栈或
-绕过清理之前,强制执行运行时配置的深度限制。超过配置深度的格式错误输入应使根操作
-失败,而不是继续无界递归。
-
-消耗编码数据的循环应保证字节进度、逻辑进度或终止错误。即使不分配内存,能让读取器
-陷入无进度循环的输入也与安全相关。
-
-## 安全不变量
-
-反序列化代码必须防止不可信输入导致以下结果:
-
-- 崩溃、panic、未定义行为或越界内存访问。
-- 与已经提供或确认可读的字节相比发生 OOM 或不成比例的分配。
-- 无进度循环,包括格式错误输入之后既不能保证逻辑进度也不能保证字节进度的循环。
-- 在准确读取或跳过相应字节之前,将流缓冲区增长到攻击者声明的大小。
-- 资源泄漏,包括无法执行清理的原生内存分配、句柄、回调或已注册清理工作。
-- 失败后保留攻击者控制的状态,且该状态会影响后续根操作或跨操作增长。
-- 成功绕过显式 Fory 策略边界。
-
-如果某条路径无法产生上述任何结果,更早拒绝格式错误的字节通常属于正确性或互操作性
-选择,而不是安全要求。
-
-## 健壮性范围关卡
-
-报告或修复反序列化健壮性问题之前,请先确认当前实现中存在具体后果:
-
-- 崩溃、panic、未定义行为或越界访问。
-- 不成比例的分配、CPU 工作量或流增长。
-- 无进度循环。
-- 持久状态、引用表或缓存污染。
-- 后续根操作损坏或失败根操作的清理泄漏。
-- 具体的类型、注册、可调用对象或反序列化策略违规。
-
-单纯的协议严格性不属于此关卡。不要仅仅因为格式错误或非规范的标志、枚举值、标记、
-长度形式或保留值被接受、较晚拒绝、以不同方式解码或产生不够精确的错误就修改代码。
-只有当此类验证能防止上述具体后果之一,或实现显式公共契约时,才值得采取行动。
-
-## 受控反序列化错误
-
-当解码器确定输入对当前所有者路径无效时,根操作必须返回错误并执行正常的失败清理。
-这是结果要求,而不是错误分类体系要求。
-
-除非公共 API 或规范另有明确承诺,否则 Fory 不要求特定异常类型、错误码、消息、检测层、
-输入偏移量或尽可能早的检测点。现有的有界下游缓冲区下溢、类型、引用、深度或序列化器
-错误都是有效拒绝。解码器无需仅仅为了将这种受控失败替换为更具体或更统一的错误而添加
-新的局部检查。
-
-格式错误输入的测试应证明根操作失败、清理仍然正确,并且相关安全不变量得到保留。如果
-固定确切错误类型或消息需要增加无法保护任何安全边界的成功路径验证,则不应这样做。
-
-## 非安全语义
-
-以下模式默认不属于漏洞:
-
-- 协议允许的集合分块、map 分块和字段顺序。
-- 根据目标数据结构或所属序列化器语义折叠的重复键、set 元素或兼容字段。
-- 最终会产生读取错误的格式错误 ref、null 或类型标志。
-- 线性消耗并最终产生读取错误的格式错误标量字节。
-- 在之后的形状验证前读取编码主体,只要操作最终返回错误且不会造成安全不变量失效。
-- 实体化组件类型是已被允许作为类 token 的接口的数组。分配引用数组不会实例化或执行
- 该接口,每个非空元素仍必须针对其具体类型通过当前策略。只有当数组路径绕过该具体
- 元素检查、调用策略禁止的回调,或违反 Fory 所有的运行时安全或资源不变量时,才将其
- 视为与安全相关。
-
-Fory 仍可出于规范严格性或互操作性拒绝格式错误的形式。只有当协议所有者要求、在相关
-路径上几乎没有成本,或能保护上述安全不变量时,才应添加此类验证。不要仅仅为了拒绝
-只会增加解码成本的标量字节形式而添加协议层验证。
-
-### 携带值的 ref 标志
-
-某些读取路径有意为多个携带值的标志共享处理。例如,当 `NotNullValue` 和 `RefValue` 都
-表示后续是编码值时,读取器可以合并其热路径处理。这本身不是格式错误标志 bug。只有
-当合并处理丢失必需的引用语义、跨越显式所有者策略返回成功,或造成资源或运行时安全
-失效时,才将其视为 bug。
-
-## 分配与字节可用性
-
-在所需字节可用或已被准确读取之前,Fory 不应根据攻击者声明的长度进行大额分配。
-
-对于缓冲区支持的输入:
-
-- 固定大小的二进制值和基本类型稠密数组应在分配目标之前,针对所需编码字节大小调用
- 字节所有者的可读性检查。对于缓冲区支持的输入,这通常是剩余字节比较。
-- 多字节元素数组应在分配前通过溢出检查计算所需字节大小。
-- 根据声明的逻辑元素计数分配后备存储或提供大小提示的容器读取器,应在分配后备存储
- 或预留容量前,针对该计数调用字节所有者的可读性检查。这不是完整的容器主体验证;
- 它用于证明在读取器根据计数预分配之前,发送方至少提供了成比例的输入字节。估算的
- 内存预算核算可以在此字节检查前预留预算,因为它不分配后备存储。
-- 当目标逐步增长且每个条目的读取仍使用常规字节所有者检查时,读取器不应仅仅因为循环
- 将读取相应数量的值,就添加基于计数的可读性检查。安全边界是根据不可信计数直接
- 预分配,而不是存在计数循环本身。
-
-对于流支持的输入:
-
-- 读取或跳过较大的字节区域,就是这些字节存在的证明。
-- 按字节计数的可变长度结果分配应在分配前使用字节所有者的可读性检查。跳过路径可以
- 使用有界跳过,而无需实体化被跳过的值。
-- 从流中读取完整请求编码主体后,流支持的缓冲区可以保存该主体。但在输入字节证明
- 攻击者声明的长度确实存在之前,不得预留该长度。
-- 流支持的填充缓冲区应从当前已证明的缓冲区大小按几何级数增长,例如将当前容量翻倍。
- 增长不得限制为当前填充目标:对于较小填充,目标仅略高于当前容量,因此限制到目标
- 会退化为固定大小的增长步骤,在每次小读取时复制整个缓冲区,使流反序列化总体成为
- O(n^2)。当流实现本身是调用方拥有的可信代码时,字节所有者可以使用所有者局部可用性
- 信号作为一次性增长提示,随后可以一次预留完整的当前目标,同时至少保留几何增长步长;
- 如果该提示不存在或不足,读取器必须回退到基于已缓冲字节的有界几何增长。序列化器
- 不应添加自己的可用性分支。
-- 截断的流应在分配最终反序列化值前失败,并且只为实际读取的字节加上有界备用容量
- 进行分配。
-
-字节所有者应保持面向字节。缓冲区、读取器或读取上下文 API 可以公开字节读取和字节跳过
-操作,但字符串解码、十进制解析、基本类型数组编码、压缩模式和集合容量策略属于各自的
-所属序列化器。
-
-## 集合与 Map 容量
-
-允许较大的有效集合输入。如果输入包含许多编码元素,应进行成比例的反序列化工作。
-
-安全要求是在足够输入字节证明该容量合理之前,避免根据声明的逻辑计数进行不成比例的
-预分配。当重复的元素或条目主体经证明至少消耗一个字节时,根据声明计数进行分配或预留
-的读取器应在分配前调用 `checkReadableBytes(logicalCount)` 或运行时等价方法。当主体可能
-不消耗字节时,可读字节要求可以排除根操作剩余的无输入支撑容器余量。读取器在读取容器
-时仍必须核算实际输入进度。字节检查不会解码整个容器、验证元素语义,也不能替代分块
-验证。不根据逻辑计数预分配的读取器,仍可随实际读取元素而成比例增长。
-
-只有当缺少验证会导致无进度循环、无界资源增长、保留状态或跨越 Fory 策略边界返回成功
-时,map 或集合分块验证才与安全相关。协议允许的分块划分属于正常输入,本身不是安全问题。
-
-## 无输入支撑容器工作量预算
-
-运行时会对由计数驱动、且重复读取主体没有输入进度支撑的集合元素和 map 条目执行根操作
-范围限制。公共选项名为 `maxUnbackedContainerItems`,或使用相应语言中的等价拼写。
-默认值为 `8192`;值必须非负,零是严格限制,而不是表示无限制的哨兵值。
-
-一次根读取中的所有嵌套集合、map 和兼容字段跳过操作共享此余量。集合读取器每完成 1024
-个元素以及在最终不完整窗口处核算已完成条目。Map 读取器在现有协议分块边界处核算。
-重复条目主体实际消耗的字节会抵消同一窗口内的已完成条目数。该预算不会添加成帧、在
-写入时拒绝值、改变引用发布,也不会替代对象图内存核算。
-
-如果读取器确切的重复操作已知至少消耗一个字节,则保留直接循环和成比例的可读字节检查。
-生成和编译的序列化器应从这些已证明为正的路径中移除预算访问和周期性分支。
-
-## 对象图内存预算
-
-运行时应对一个实体化对象图创建的估算内存执行每操作近似限制。这是对一次顶层反序列化
-操作创建的对象图所有者进行累计核算;它不是精确的堆测量,也不是原始元素槽位限制。
-实际进程内存可能高于配置的限制。
-
-公共配置为 `maxGraphMemoryBytes`。所有输入形式的默认值固定为 `128 MiB`;用户配置的正值
-会覆盖默认值。显式的非正配置无效,应在创建运行时时拒绝。预算不从输入大小派生,流预算
-也不应依赖动态已读字节核算。
-
-对象图预算核算应:
-
-- 在顶层读取状态中初始化,并由顶层反序列化 `finally` 负责清理;
-- 只核算 Fory 创建并由返回值对象图保留的对象或存储;仅在构造期间使用的临时辅助对象
- 不属于对象图预算;
-- 不声称能够预算显式策略允许相应代码后执行的任意构造函数、可调用对象、描述符、
- finalizer 或状态恢复内部资源;
-- 将读取上下文/读取状态限制为原始字节预留;计数运算以及集合、map、数组、结构体和对象
- 存储公式属于具体序列化器或生成序列化器所有者;
-- 在比较预算或分配前拒绝算术溢出;
-- 估算浅层所有者存储的下限:引用支持或堆实体化的集合、map、set 和引用数组预留非零
- 浅层自身成本加上后备/引用/内联存储;引用支持或堆实体化的 struct、record、POJO、
- tuple/product、兼容、生成和动态对象所有者预留非零浅层自身成本加上浅层字段存储;
-- 当实际引用槽位大小无法低成本或可靠查询时,使用 4 字节引用槽位;内联存储使用基本
- 类型/值字段宽度;
-- 在分配后备存储或预留容量前保留现有字节可用性检查;
-- 跳过作为独立所有者的 enum/union,并跳过专用的 string、binary、基本类型标量、基本
- 类型数组和基本类型稠密数组叶子所有者,除非运行时特有所有者章节明确将其纳入。
-
-被跳过的叶子所有者仍必须受剩余输入字节限制。如果未读输入没有足够字节表示 string、
-binary 值、基本类型标量、基本类型数组或基本类型稠密数组,运行时不得读取或创建该叶子值。
-
-每个运行时都必须在选择公式前检查具体所有者路径。在存储、装箱或分配值的所有者处恰好
-预留一次自身存储。反序列化 facade 可以为每次操作重置预算,但不得预留顶层结果类型、
-自身字节或值存储。引用支持的路径预留父所有者自身成本加上引用存储,而每个被引用的堆
-所有者在实体化时预留自己的浅层自身成本。内联/值路径在持有者/分配所有者中预留内联
-元素、字段或装箱存储;顶层值序列化器和生成的 struct/product 读取路径不得计算自身存储。
-对于内联/值集合或 map 运行时,反序列化 facade 或容器序列化器不得仅仅因为顶层值容器
-是返回值就对其计费。嵌套值容器作为父持有者的内联槽位,或作为实际拥有这些槽位的外层
-集合后备存储元素计费。指针、box、智能指针或类型擦除实体化路径为其分配的堆值预留浅层
-存储。父所有者不得递归包含子对象、集合、map、string、binary 或基本类型稠密数组的内容;
-子所有者在实体化时预留自己的浅层内存。
-
-### Java Fory 核心
-
-Java Fory 核心基本类型数组序列化器预留可移植数组头部,加上逻辑长度乘以基本类型存储
-宽度。基本类型 list 序列化器预留返回 list 的浅层所有者、后备数组头部以及相同的基本
-类型存储。这些已知长度路径在现有成比例可读字节检查后、分配前预留一次;它们不使用
-增量批次。压缩输入使用解压后的逻辑长度,而临时压缩数组仍是保留对象图预算之外的构造
-暂存。Float16 和 BFloat16 稠密数组载体还包括其包装器的浅层所有者。当装箱 list 转换
-首先解码基本类型数组时,数组预留仍作为最终 list 估算的抵扣,转换只预留正的剩余差值。
-
-### 生成的结构化目标
-
-编码成员和物理存储是不同的输入。属性、访问器、接口和逻辑 Schema 别名不是物理字段,
-不得作为存储计费。既序列化又存储的字段只计数一次。仅存储声明贡献其字段宽度,但不得
-进入编码元数据或生成的读写操作。
-
-对于 C# 普通类,每个直接带注解的类拥有该类声明的物理实例字段。可继承类提供方发布
-累计父提供方值加上这些直接字段。密封具体序列化器在内部使用相同累计表达式。具体后代
-使用可立即访问的提供方值及其自身直接字段;不得枚举被引用的私有元数据或重建父存储。
-具体对象序列化器预留一个浅层对象所有者加上此累计字段存储。
-
-C# 外部类声明拥有其列出的确切第三方物理字段。确切字段映射贡献存储。可见属性映射不
-贡献存储,因此其后备字段必须单独列出。忽略的映射必须标识一个确切类字段,且仅用于
-存储。外部 struct 声明仅支持可见成员映射。可发现但未映射的公共实例字段可以添加一次。
-`BaseOnly` 声明可以拥有普通子类使用的完整目标和目标祖先前缀。它必须列出该前缀中的
-每个非公共物理字段;生成器不会扫描被引用 assembly 的私有布局。
-
-确切外部私有身份是固定到版本的软件包 ABI 断言。运行时编码访问使用确切访问器,不得
-回退到反射、布局探测或其他成员。仅存储的私有声明没有运行时访问器,因此应用必须根据
-固定的软件包版本验证它们。
-
-Dart 生成器还可以包含生成时在目标上可见的公共实例字段。Swift 宏无法检查其他类型的
-存储布局,因此只使用外部声明。在每个运行时中,这些公式都在生成期间解析,不得向
-反序列化热路径添加反射、布局探测、分配或字段枚举。常规所有者规则仍然适用:引用目标
-预留其浅层所有者和字段存储,而内联值目标由拥有其存储的持有者计费。
-
-### 运行时特有所有者说明
-
-#### C++
-
-C++ 普通 struct、product 和标准库容器属于值存储,除非指针、智能指针或类型擦除所有者
-在堆上分配它们。顶层反序列化会初始化剩余对象图预算,但不会为返回值预留 `sizeof(T)`。
-普通值序列化器不得仅仅因为正在读取值,就预留自身的 `sizeof(T)`。
-
-泛型集合和 map 序列化器预留容器路径拥有的元素、键和值存储下限。嵌套值容器头部作为
-父对象的内联槽位或外层容器后备存储中的元素时计费。智能指针和类型擦除实体化路径在
-发布或返回其分配的堆值前,为该值预留浅层存储。泛型 C++ 路径不得臆造标准库头部、
-节点、桶、分配器或调试布局开销。
-
-#### Rust
-
-Rust struct、tuple、enum 和集合值属于内联值存储,除非由 `Box`、`Rc`、`Arc` 或类型擦除
-所有者分配。顶层和派生值读取路径初始化或消耗预算,但不为正在读取的值预留
-`size_of::<Self>()`。`Vec`、`HashMap`、`BTreeMap` 及类似序列化器预留其根据计数分配的
-后备或条目值存储;嵌套值容器头部作为父内联字段或外层后备元素计费。
-
-装箱、引用计数和类型擦除实体化路径为其创建的堆载荷预留 `size_of::<T>()`。这些分配
-所有者可以使用编译期 `size_of::<T>()` 公式,但值序列化器不应为同一个 `T` 添加并行
-自身预留。
-
-在根据计数分配前,如果 Rust 所有者的确切重复操作已证明至少消耗一个字节,则保留完整
-可读字节关卡。不确定的所有者只需对计数中未被剩余无输入支撑条目余量覆盖的部分要求
-可读字节。在分配所有者处恰好应用一次所选关卡;读取共享元数据后不要重复。写入端继续
-编码合法的紧凑或空主体,不执行此读取端余量。
-
-固定数组不根据经过验证的编码计数进行分配,因此省略分配关卡。对于零大小元素,`Vec`、
-`VecDeque` 和 `BinaryHeap` 也省略该关卡,因为在这种情况下它们不会创建根据计数派生的
-后备分配。在声明计数驱动分配时,节点、桶和条目所有者保留该关卡。实现不得以猜测的
-分配成本、填充字节、全局紧凑主体绕过或第二个集合/map codec 取而代之。
-
-#### Swift
-
-Swift struct、enum、tuple 和集合值属于值存储。顶层值读取和嵌套值序列化器不应预留
-自身存储。拥有该值的持有者(例如 struct 字段、数组后备存储、dictionary 条目存储或
-装箱/动态实体化路径)拥有相应的对象图预算预留。
-
-数组、dictionary 和 set 序列化器分配或预留后备存储时,可以使用稳定的 Swift 类型大小
-信息(例如 `MemoryLayout<T>.stride`)预留后备存储下限。当 Fory 创建保留的对象或 box
-时,类、existential 或装箱实体化路径预留所有者存储。不应向读取热路径添加运行时对象
-布局探测。
-
-#### Go
-
-Go struct 以及 slice 或 map 头部属于值存储,除非由指针、接口实体化或其他堆所有者分配。
-顶层反序列化和 struct 值序列化器不应自行预留返回的 struct 或嵌套内联 struct。指针
-序列化器在分配保留的 `*T` 时预留具体 struct 存储。
-
-Slice、array、map 和 set 序列化器预留其根据声明计数分配的后备或条目存储。元素和条目
-宽度应尽可能来自序列化器或解析器捕获的稳定类型信息;如果所有者已经知道具体类型,读取
-循环不应重新计算反射大小信息。接口或动态路径只预留
-Fory 明确实体化并保留的存储。
-
-#### C\#
-
-C# 同时包含引用所有者和内联值类型。类、数组、list、dictionary、hash set 和其他堆容器
-预留非零浅层所有者成本,加上直接后备、引用槽位或内联元素存储。即使 dictionary 的键
-或值类型是 struct,dictionary 也是引用类型容器,因此 dictionary 所有者仍与其条目存储
-分开计费。
-
-值 struct 内联读取时不预留自身存储;存储该 struct 的持有者(例如对象字段、数组元素、
-list 后备存储、dictionary 条目、box 或动态实体化路径)拥有该预留。当 Fory 创建保留的
-box 时,装箱、`object` 和动态实体化路径预留一个装箱所有者。所有者常量应是相关 C#
-对象或容器形状真正可移植的下限,而不是占位标记。
-
-运行时不应猜测对象头部、数组头部、分配器头部、调试模式字段、hash 桶、树链接、hash 链
-链接、节点头部、map 条目对象、备用块或运行时表布局,除非所有者路径具有低成本、稳定、
-显式的存储下限信号并记录了公式。所有者常量应是所有者形状的真实下限,而不是占位标记。
-
-## 跳过语义
-
-跳过未知或不兼容数据时,应根据具体影响进行分类,而不是根据运行时是否实体化临时值。
-
-当直接消耗编码内容很简单且由当前运行时路径负责时,这样做很有用。对于 list、set 和 map
-等复杂字段,这不是安全要求。如果能够保留现有序列化器所有权模型,运行时可以实体化值
-然后将其丢弃。
-
-对于扩展、动态或用户拥有的类型,所属运行时可能不总是有足够信息在不调用已注册序列化器
-的情况下跳过。此时应根据具体影响分类行为:
-
-- 资源泄漏、保留状态、无进度循环或策略绕过与安全相关。
-- 允许有界实体化后返回错误或丢弃,除非它造成显著内存或 CPU 压力。
-- 仅要求被跳过值使用某一种特定编码形状的严格性,不是安全问题。
-
-## 元数据与类型解析
-
-当元数据解析影响保留的读取端状态、类型分派或策略决策时,它对安全敏感。
-
-元数据读取器应:
-
-- 避免嵌套元数据结构中的无界递归。
-- 避免攻击者控制的元数据流导致表无界增长。
-- 在使用元数据主体绕过或替换现有策略决策前验证它们。
-- 对于 Java 元数据路径,将 `TypeChecker` 等名称级检查和禁止类列表保持在
- `Class.forName` 之前;为此,通过现有 `TypeResolver.loadClass` 所有者路由远端类名加载。不要通过直接从
- TypeDef 或 TypeMeta 名称加载类来绕过该所有者。被拒绝的输入名称不得导致类加载。
- 将此决策移动到加载前时,应保留注册、动态加载和未知类型语义。需要实体化
- `Class<?>` 的检查仍在加载后执行;不要用仅基于字符串的近似检查替代它们。
-- 将完整的输入数组描述符传给 `TypeChecker`。输入最多可从已接受组件类派生六维数组。
- 更高维数组要求精确可信的完整数组注册或经过检查的名称缓存条目,使输入无法让 JVM
- 派生无界的数组类系列。
-- 在正确的根操作边界重置或释放元数据状态。
-
-不可信反序列化可以访问的类解析缓存,只能从显式可信配置发布条目,或在当前类策略接受
-解析出的类后发布条目。因此,缓存命中表示已经可信且验证过的 `Class<?>`,应直接使用
-缓存类,而不重复类加载或名称级 `TypeChecker` 工作。只有缓存未命中才执行这些名称级
-检查并发布接受的结果。需要实体化 `Class<?>` 的检查仍由其现有调用方负责。存储纯数据
-未知类占位符的缓存条目可以在精确命中时返回同一个占位符,但不得授权加载最初未命中的
-编码名称。对于 ID 注册和名称注册,精确的已注册名称表命中都是可信的;经过检查的名称
-缓存精确命中也是可信的。两个精确查找都未命中后,读取器不得从反向注册、以类为键的
-状态或 `Class.getName()` 推断另一个已接受名称。自定义名称注册本身不会将 Java 类名发布
-为额外别名;ID 注册会发布 Java 类名。
-
-能够创建持久读取状态的远端元数据,必须在保留该状态前受到限制。该检查仅用于资源控制:
-不得改变编码兼容性、类型注册、动态类加载、未知类型处理、反序列化策略或 Schema 演进
-语义。失败或不兼容的元数据不得消耗 Schema 版本限制;元数据缓存命中或生成的字段读取器
-不得为这些限制添加验证、hash、分配或策略工作。元数据解析、缓存发布、精确本地匹配和
-计数的具体顺序属于 [xlang 实现指南](../specification/xlang_implementation_guide.md)。
-
-经过检查的元数据缓存,是判断接收的 TypeDef 或 TypeMeta 头部是否已经验证的唯一所有者。
-元数据缓存命中意味着该头部先前已被解析、通过主体/hash 验证和策略检查,并由所属缓存
-发布,因此读取器必须跳过剩余元数据主体并使用缓存元数据,不重复主体验证、hash 验证、
-限制检查、精确本地检查或策略工作。元数据缓存未命中是唯一会解析元数据主体、验证其
-hash 和形状、执行元数据限制、进行精确本地字节比较并发布到缓存的路径。不要添加单独的
-可空标志、哨兵头部、每 TypeInfo 接受标记或并行状态来表示此决策。
-
-只有实际作为 TypeDef 或 TypeMeta 主体携带的元数据才受元数据主体和 Schema 版本限制。
-兼容的命名 enum、ext 和 union 元数据通常只有一个版本,但当它作为共享元数据发送时,
-仍计入远端元数据总量限制。纯基于 ID 的 enum、ext 和 typed-union 值使用 type id 加 user
-type id,不得移到此元数据主体路径。
-
-远端元数据主体和 struct 字段列表也必须在冷元数据解析路径上受到限制。
-`maxTypeMetaBytes` 限制一个接收的 TypeDef 或 TypeMeta 主体中的编码元数据主体字节数,
-不包括 8 字节头部和任何扩展大小 varint。`maxTypeFields` 限制一个接收的 struct 元数据
-主体声明的字段数量。对于 Java 原生 TypeDef 类层级,字段限制适用于该 TypeDef 所有类
-层级的字段总数。这些限制在根据攻击者声明的元数据大小或字段计数进行复制、解压、预留
-或分配之前检查。
-
-默认限制为 `maxTypeFields = 512` 和 `maxTypeMetaBytes = 4096`。运行时应将限制失败报告为
-可能的恶意数据,并告知用户只有在数据没有恶意时才提高确切选项。这些限制不得在元数据
-缓存命中、生成的序列化器或已解析 type-id 热路径上引入验证。
-
-单纯的元数据字节形式严格性不是安全要求。只有当所有者需要这种严格性,或该形状会改变
-类型身份、保留状态、资源使用或策略行为时,拒绝元数据形状才有意义。
-
-## 引用跟踪
-
-引用跟踪是编码协议的一部分,并且对性能敏感。读取器可以使用哨兵值和共享的携带值分支,
-以保持热路径紧凑。
-
-当格式错误输入能够导致以下结果时,引用跟踪验证与安全相关:
-
-- 访问越界引用而不报告错误。
-- 根操作失败后留下保留的引用状态。
-- 在引用值可用之前注册无界回调或解析器状态。
-- 导致无进度循环或崩溃。
-
-不能仅仅因为格式错误的标志没有在最早可能的字节处被拒绝,就要求进行引用跟踪验证。
-只要根操作仍返回错误且没有违反安全不变量,延迟拒绝就是可接受的。下游错误无需是专用
-引用协议错误。
-
-## 错误传播与清理
-
-Fory 运行时可以有意使用延迟错误传播。读取记录错误后,后续读取步骤可以继续,直到外层
-操作观察到并返回该错误。
-
-如果继续执行的工作不会导致以下结果,则可以接受:
-
-- 崩溃或 panic。
-- 分配或保留攻击者控制的状态。
-- 泄漏资源。
-- 绕过必需的清理。
-- 跨越显式验证或策略边界返回成功。
-
-只有当外层根操作清理无法覆盖嵌套路径拥有的状态或资源时,才应添加嵌套
-`try`/`finally` 或等价清理。
-
-## 性能要求
-
-安全验证必须保持 Fory 热路径性能。如果验证仅用于严格性且会引入以下内容,请勿添加:
-
-- 每元素对象分配。
-- 热循环中的动态分派或回调。
-- 成功路径上的包装对象或结果载体。
-- 缓冲区支持的 string、binary 或基本类型数组读取所需的额外复制。
-- 无法保护安全不变量的分支。
-- 唯一目的只是统一最终错误类型、消息、位置或时机的辅助调用或生成代码膨胀。
-
-优先使用可以内联、且已经利用当前序列化器可用信息的所有者局部检查。不要将序列化器
-拥有的语义移入通用读取上下文辅助方法。
-
-## 分类指南
-
-审查反序列化行为时,请使用以下问题:
-
-1. 此输入能否导致崩溃、panic 或越界内存访问?
-2. 较小或未经证明的输入长度能否导致不成比例的分配?
-3. 流支持的读取器能否在精确读取或跳过证明字节存在之前增长缓冲区?
-4. 循环能否在没有字节进度或逻辑进度的情况下继续?
-5. 根操作失败后,该路径能否保留攻击者控制的状态?
-6. 该路径能否泄漏资源或跳过必需的清理?
-7. 该路径能否跨越显式 Fory 策略边界返回成功?
-8. 所提议的验证在相关热路径上是否几乎没有成本?
-
-如果前七个问题的答案都是否,该问题通常不属于安全问题。如果验证并非几乎没有成本,
-除非协议所有者明确要求,否则请避免添加。
-
-## 文档边界
-
-安全模型文档不得包含利用样例、CVE 叙述、行级潜在漏洞、分支历史、迁移时间线或清理计划。
-请根据需要将这些细节保留在私有报告、issue 或 pull request 中。
-
-公开安全文档应描述持久边界和不变量,而不是实现如何达到这些状态的历史。
diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/index.md
index 658e097eac..832a38dd4f 100644
--- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/index.md
+++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/index.md
@@ -37,11 +37,23 @@ Fory JSON 是 Java JSON codec;如果目标不是重建对象,请使用
选择运行时以查看其安装方式、生命周期、具体 API、配置、类型注册、Schema 行为、扩展、
平台支持和故障排除信息:
-[浏览支持的运行时](runtimes.md)
+| 运行时 | 模式 | 文档 |
+| --------------------- | ----------------- | ----------------------------------------------------- |
+| Java | xlang 和原生 | [Java 运行时](./java/index.md) |
+| Python | xlang 和原生 | [Python 运行时](./python/index.md) |
+| C++ | xlang 和原生 | [C++ 运行时](./cpp/index.md) |
+| Go | xlang 和原生 | [Go 运行时](./go/index.md) |
+| Rust | xlang 和原生 | [Rust 运行时](./rust/index.md) |
+| JavaScript/TypeScript | xlang | [JavaScript/TypeScript 运行时](./javascript/index.md) |
+| C# | xlang | [C# 运行时](./csharp/index.md) |
+| Swift | xlang | [Swift 运行时](./swift/index.md) |
+| Dart | xlang | [Dart 运行时](./dart/index.md) |
+| Scala | xlang 和 JVM 原生 | [Scala 运行时](./scala/index.md) |
+| Kotlin | xlang 和 JVM 原生 | [Kotlin 运行时](./kotlin/index.md) |
## 安全性
-解码外部提供的字节之前,请阅读[对象序列化安全性](security.md)。其中介绍了两种模式下的
+解码外部提供的字节之前,请阅读[安全](security.md)。其中介绍了两种模式下的
可接受类型策略、注册、资源限制、传输责任和负向验证。
## 规范
diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/runtimes.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/runtimes.md
deleted file mode 100644
index 19e4bed27e..0000000000
--- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/runtimes.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: 对象序列化运行时
-sidebar_position: 3
-id: runtimes
-license: |
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
----
-
-请选择应用所使用 API 对应的运行时。运行时文档仅涵盖二进制对象序列化;Row Format、
-Fory JSON、编译器和 gRPC 分别由各自的能力指南说明。
-
-| 运行时 | 模式 | 文档 |
-| --------------------- | ----------------- | ----------------------------------------------------- |
-| Java | xlang 和原生 | [Java 运行时](./java/index.md) |
-| Python | xlang 和原生 | [Python 运行时](./python/index.md) |
-| C++ | xlang 和原生 | [C++ 运行时](./cpp/index.md) |
-| Go | xlang 和原生 | [Go 运行时](./go/index.md) |
-| Rust | xlang 和原生 | [Rust 运行时](./rust/index.md) |
-| JavaScript/TypeScript | xlang | [JavaScript/TypeScript 运行时](./javascript/index.md) |
-| C# | xlang | [C# 运行时](./csharp/index.md) |
-| Swift | xlang | [Swift 运行时](./swift/index.md) |
-| Dart | xlang | [Dart 运行时](./dart/index.md) |
-| Scala | xlang 和 JVM 原生 | [Scala 运行时](./scala/index.md) |
-| Kotlin | xlang 和 JVM 原生 | [Kotlin 运行时](./kotlin/index.md) |
diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/security.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/security.md
index d1ab7a39dd..46d4e8bc1c 100644
--- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/security.md
+++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/object-serialization/security.md
@@ -1,6 +1,6 @@
---
-title: 对象序列化安全性
-sidebar_position: 4
+title: 安全
+sidebar_position: 99
id: security
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
@@ -19,7 +19,7 @@ license: |
limitations under the License.
---
-本指南定义 Fory 二进制对象序列化在 xlang 和原生模式下的信任边界与安全运行模型。面向贡献者的分类规则位于[反序列化安全模型](deserialization-security-model.md)。
+本指南定义 Fory 二进制对象序列化在 xlang 和原生模式下的信任边界与安全运行模型,同时提供审查不可信反序列化路径时使用的贡献者分类规则。
Fory 是进程内序列化库。应用将 Fory 链接到自身进程中,配置序列化器和类型策略,并调用
Fory API 序列化应用拥有的对象或反序列化已编码的 Fory 数据。Fory 不提供独立的网络
@@ -47,7 +47,7 @@ Fory 安全边界包括:
类注册时,`TypeChecker` 或等价白名单策略就是相关关卡。生成的序列化器代码派生自经过
检查的类型描述符,而不是攻击者控制的字节内容。
-[反序列化安全模型](deserialization-security-model.md)定义了如何为不可信反序列化路径
+[安全审查模型](#安全审查模型)定义了如何为不可信反序列化路径
分类这些边界。
## 非目标
@@ -74,7 +74,7 @@ Fory 不提供:
对可信数据禁用注册或使用动态反序列化属于配置选择。对于不可信数据,绕过显式 Fory
策略、崩溃、泄漏资源、保留攻击者控制的状态或不成比例地分配资源,仍然属于
-[反序列化安全模型](deserialization-security-model.md)所述的安全问题。
+[安全审查模型](#安全审查模型)所述的安全问题。
## 资源限制
@@ -164,3 +164,517 @@ Fory fory =
- 同一个可复用运行时中格式错误的根之后紧跟的有效根。
还应独立于 Fory 验证应用的外部身份验证、完整性、请求大小、超时和领域验证控制。
+
+## 安全审查模型
+
+本文档定义 Apache Fory 反序列化的安全模型。它是用于分类反序列化行为并确定何处需要
+验证的公开安全参考。它不是漏洞披露,不描述利用技术,也不记录实现历史。
+
+该模型有意保持较窄的范围。Fory 应防止不可信输入导致的资源和策略失效,但当验证无法
+保护 Fory 安全边界时,不应在热路径上添加只用于强制字节形式严格性的验证。
+
+### 范围
+
+该模型仅适用于反序列化来自不可信或部分可信来源的 Fory 二进制对象序列化数据。Fory
+JSON 使用不同的读取器、策略、codec 和资源核算规则,因此有单独的
+[安全指南](../json/security.md)。
+
+该模型不将成功反序列化值的语义内容视为 Fory 安全边界。发送方始终可以构造协议有效、
+但值由其自行选择的数据。应用授权、对象级业务规则和领域特有验证仍由应用负责。所选择
+的业务不变量仍属于应用策略,而不是 Fory 协议安全边界。
+
+除非某个运行时通过不可信反序列化 API 显式公开内存格式路径,否则该模型不管理此类路径。
+
+### 信任边界
+
+在接受外部字节或流的 API 边界上,Fory 反序列化应将编码输入视为不可信。
+
+Fory 安全边界包括:
+
+- 资源所有权,例如内存、CPU 进度、流缓冲、文件句柄、原生内存分配、回调和保留的
+ 读取端表。
+- 运行时安全,例如避免崩溃、panic、未定义行为和越界读写。
+- 显式 Fory 策略检查,例如旨在限制可实体化内容的类型、函数、方法、类或注册策略。
+- 清理边界,即读取失败期间创建的状态必须在下一次根操作前释放或重置。
+
+Fory 安全边界不包括:
+
+- 协议有效值的业务含义。
+- 一个值使用了哪一种协议允许的字节形式。
+- map、set、对象或元数据值是否使用某一种特定编码形状,除非拒绝其他形状属于显式
+ 所有者策略,或能够保护上述边界之一。
+
+### 类型与类策略
+
+当类型、类、函数、方法、注册和反序列化策略旨在限制不可信字节可以实体化的内容时,
+这些策略就是安全边界。
+
+对于不可信数据,如果编码字节能够实体化当前 Fory 策略本应拒绝的类型、函数、方法、类
+或动态对象,则绕过行为与安全相关。这包括绕过类或类型注册、白名单检查器、严格模式
+检查或语言特有的反序列化策略。
+
+应用注册一个类或为该类注册序列化器时,即显式信任该类。在类注册策略下,这两项操作
+都是配置时信任决策。在反序列化调用处显式选择静态根序列化器或静态根目标,也是应用
+对该根路径的授权决策。静态选择根的授权不依赖单独的注册查找;访问已注册身份或注册
+支持的元数据所需的任何注册,仍由实际访问驱动。
+
+显式声明或选择静态字段 codec 本身就是应用对该字段的授权决策;该 codec 无需为了授权
+而单独注册。注册外围类或 Schema 也会授权属于该已注册所有者的静态声明字段 codec 和
+序列化器。这同样适用于声明的 Array、Set、Map、Struct 及其他静态组合字段路径。这些
+已声明字段路径不会仅仅因为其主体无需再次查找类型即可解码,就需要独立注册。同样,
+如果编码的声明类型标记只能调用已由授权根或外围 Schema 选择的 codec,它也不会造成
+注册绕过。
+
+这些静态授权路径不会授权由编码类型元数据选择的任意替代项。输入选择的动态或多态类型
+仍必须通过该类型当前有效的注册和反序列化策略检查。Fory 仅仅发现或生成的序列化器,
+如果不是通过显式选择的静态根或已注册外围所有者到达,就只是序列化机制,本身不会授权
+动态选择的类。
+
+对可信数据禁用注册或动态类型检查属于调用方配置选择。该选择只会移除相应策略对任意
+类型实体化提供的保证;它不会移除 Fory 对不可信反序列化路径的运行时安全、资源、清理、
+保留状态或无进度循环要求。
+
+Fory 不是应用拥有类型的沙箱。如果当前策略允许某个已注册类型或序列化器,则该类型的
+构造、hook、setter、finalizer 或其他逻辑对于应用信任边界是否安全,由应用负责。
+
+允许经过策略批准的构造或可调用对象执行时,资源核算不应声称能够约束 Fory 所有权之外
+的任意代码。Fory 拥有的核算只能覆盖由 Fory 自身明确创建或复制,并且仍可从实体化对象图
+访问的对象和存储。临时辅助分配和用户代码内部资源仍在该核算边界之外。
+
+### 深度与进度
+
+通过对象、元数据、容器或引用进行递归的反序列化路径,应在恶意构造的嵌套耗尽调用栈或
+绕过清理之前,强制执行运行时配置的深度限制。超过配置深度的格式错误输入应使根操作
+失败,而不是继续无界递归。
+
+消耗编码数据的循环应保证字节进度、逻辑进度或终止错误。即使不分配内存,能让读取器
+陷入无进度循环的输入也与安全相关。
+
+### 安全不变量
+
+反序列化代码必须防止不可信输入导致以下结果:
+
+- 崩溃、panic、未定义行为或越界内存访问。
+- 与已经提供或确认可读的字节相比发生 OOM 或不成比例的分配。
+- 无进度循环,包括格式错误输入之后既不能保证逻辑进度也不能保证字节进度的循环。
+- 在准确读取或跳过相应字节之前,将流缓冲区增长到攻击者声明的大小。
+- 资源泄漏,包括无法执行清理的原生内存分配、句柄、回调或已注册清理工作。
+- 失败后保留攻击者控制的状态,且该状态会影响后续根操作或跨操作增长。
+- 成功绕过显式 Fory 策略边界。
+
+如果某条路径无法产生上述任何结果,更早拒绝格式错误的字节通常属于正确性或互操作性
+选择,而不是安全要求。
+
+### 健壮性范围关卡
+
+报告或修复反序列化健壮性问题之前,请先确认当前实现中存在具体后果:
+
+- 崩溃、panic、未定义行为或越界访问。
+- 不成比例的分配、CPU 工作量或流增长。
+- 无进度循环。
+- 持久状态、引用表或缓存污染。
+- 后续根操作损坏或失败根操作的清理泄漏。
+- 具体的类型、注册、可调用对象或反序列化策略违规。
+
+单纯的协议严格性不属于此关卡。不要仅仅因为格式错误或非规范的标志、枚举值、标记、
+长度形式或保留值被接受、较晚拒绝、以不同方式解码或产生不够精确的错误就修改代码。
+只有当此类验证能防止上述具体后果之一,或实现显式公共契约时,才值得采取行动。
+
+### 受控反序列化错误
+
+当解码器确定输入对当前所有者路径无效时,根操作必须返回错误并执行正常的失败清理。
+这是结果要求,而不是错误分类体系要求。
+
+除非公共 API 或规范另有明确承诺,否则 Fory 不要求特定异常类型、错误码、消息、检测层、
+输入偏移量或尽可能早的检测点。现有的有界下游缓冲区下溢、类型、引用、深度或序列化器
+错误都是有效拒绝。解码器无需仅仅为了将这种受控失败替换为更具体或更统一的错误而添加
+新的局部检查。
+
+格式错误输入的测试应证明根操作失败、清理仍然正确,并且相关安全不变量得到保留。如果
+固定确切错误类型或消息需要增加无法保护任何安全边界的成功路径验证,则不应这样做。
+
+### 非安全语义
+
+以下模式默认不属于漏洞:
+
+- 协议允许的集合分块、map 分块和字段顺序。
+- 根据目标数据结构或所属序列化器语义折叠的重复键、set 元素或兼容字段。
+- 最终会产生读取错误的格式错误 ref、null 或类型标志。
+- 线性消耗并最终产生读取错误的格式错误标量字节。
+- 在之后的形状验证前读取编码主体,只要操作最终返回错误且不会造成安全不变量失效。
+- 实体化组件类型是已被允许作为类 token 的接口的数组。分配引用数组不会实例化或执行
+ 该接口,每个非空元素仍必须针对其具体类型通过当前策略。只有当数组路径绕过该具体
+ 元素检查、调用策略禁止的回调,或违反 Fory 所有的运行时安全或资源不变量时,才将其
+ 视为与安全相关。
+
+Fory 仍可出于规范严格性或互操作性拒绝格式错误的形式。只有当协议所有者要求、在相关
+路径上几乎没有成本,或能保护上述安全不变量时,才应添加此类验证。不要仅仅为了拒绝
+只会增加解码成本的标量字节形式而添加协议层验证。
+
+#### 携带值的 ref 标志
+
+某些读取路径有意为多个携带值的标志共享处理。例如,当 `NotNullValue` 和 `RefValue` 都
+表示后续是编码值时,读取器可以合并其热路径处理。这本身不是格式错误标志 bug。只有
+当合并处理丢失必需的引用语义、跨越显式所有者策略返回成功,或造成资源或运行时安全
+失效时,才将其视为 bug。
+
+### 分配与字节可用性
+
+在所需字节可用或已被准确读取之前,Fory 不应根据攻击者声明的长度进行大额分配。
+
+对于缓冲区支持的输入:
+
+- 固定大小的二进制值和基本类型稠密数组应在分配目标之前,针对所需编码字节大小调用
+ 字节所有者的可读性检查。对于缓冲区支持的输入,这通常是剩余字节比较。
+- 多字节元素数组应在分配前通过溢出检查计算所需字节大小。
+- 根据声明的逻辑元素计数分配后备存储或提供大小提示的容器读取器,应在分配后备存储
+ 或预留容量前,针对该计数调用字节所有者的可读性检查。这不是完整的容器主体验证;
+ 它用于证明在读取器根据计数预分配之前,发送方至少提供了成比例的输入字节。估算的
+ 内存预算核算可以在此字节检查前预留预算,因为它不分配后备存储。
+- 当目标逐步增长且每个条目的读取仍使用常规字节所有者检查时,读取器不应仅仅因为循环
+ 将读取相应数量的值,就添加基于计数的可读性检查。安全边界是根据不可信计数直接
+ 预分配,而不是存在计数循环本身。
+
+对于流支持的输入:
+
+- 读取或跳过较大的字节区域,就是这些字节存在的证明。
+- 按字节计数的可变长度结果分配应在分配前使用字节所有者的可读性检查。跳过路径可以
+ 使用有界跳过,而无需实体化被跳过的值。
+- 从流中读取完整请求编码主体后,流支持的缓冲区可以保存该主体。但在输入字节证明
+ 攻击者声明的长度确实存在之前,不得预留该长度。
+- 流支持的填充缓冲区应从当前已证明的缓冲区大小按几何级数增长,例如将当前容量翻倍。
+ 增长不得限制为当前填充目标:对于较小填充,目标仅略高于当前容量,因此限制到目标
+ 会退化为固定大小的增长步骤,在每次小读取时复制整个缓冲区,使流反序列化总体成为
+ O(n^2)。当流实现本身是调用方拥有的可信代码时,字节所有者可以使用所有者局部可用性
+ 信号作为一次性增长提示,随后可以一次预留完整的当前目标,同时至少保留几何增长步长;
+ 如果该提示不存在或不足,读取器必须回退到基于已缓冲字节的有界几何增长。序列化器
+ 不应添加自己的可用性分支。
+- 截断的流应在分配最终反序列化值前失败,并且只为实际读取的字节加上有界备用容量
+ 进行分配。
+
+字节所有者应保持面向字节。缓冲区、读取器或读取上下文 API 可以公开字节读取和字节跳过
+操作,但字符串解码、十进制解析、基本类型数组编码、压缩模式和集合容量策略属于各自的
+所属序列化器。
+
+### 集合与 Map 容量
+
+允许较大的有效集合输入。如果输入包含许多编码元素,应进行成比例的反序列化工作。
+
+安全要求是在足够输入字节证明该容量合理之前,避免根据声明的逻辑计数进行不成比例的
+预分配。当重复的元素或条目主体经证明至少消耗一个字节时,根据声明计数进行分配或预留
+的读取器应在分配前调用 `checkReadableBytes(logicalCount)` 或运行时等价方法。当主体可能
+不消耗字节时,可读字节要求可以排除根操作剩余的无输入支撑容器余量。读取器在读取容器
+时仍必须核算实际输入进度。字节检查不会解码整个容器、验证元素语义,也不能替代分块
+验证。不根据逻辑计数预分配的读取器,仍可随实际读取元素而成比例增长。
+
+只有当缺少验证会导致无进度循环、无界资源增长、保留状态或跨越 Fory 策略边界返回成功
+时,map 或集合分块验证才与安全相关。协议允许的分块划分属于正常输入,本身不是安全问题。
+
+### 无输入支撑容器工作量预算
+
+运行时会对由计数驱动、且重复读取主体没有输入进度支撑的集合元素和 map 条目执行根操作
+范围限制。公共选项名为 `maxUnbackedContainerItems`,或使用相应语言中的等价拼写。
+默认值为 `8192`;值必须非负,零是严格限制,而不是表示无限制的哨兵值。
+
+一次根读取中的所有嵌套集合、map 和兼容字段跳过操作共享此余量。集合读取器每完成 1024
+个元素以及在最终不完整窗口处核算已完成条目。Map 读取器在现有协议分块边界处核算。
+重复条目主体实际消耗的字节会抵消同一窗口内的已完成条目数。该预算不会添加成帧、在
+写入时拒绝值、改变引用发布,也不会替代对象图内存核算。
+
+如果读取器确切的重复操作已知至少消耗一个字节,则保留直接循环和成比例的可读字节检查。
+生成和编译的序列化器应从这些已证明为正的路径中移除预算访问和周期性分支。
+
+### 对象图内存预算
+
+运行时应对一个实体化对象图创建的估算内存执行每操作近似限制。这是对一次顶层反序列化
+操作创建的对象图所有者进行累计核算;它不是精确的堆测量,也不是原始元素槽位限制。
+实际进程内存可能高于配置的限制。
+
+公共配置为 `maxGraphMemoryBytes`。所有输入形式的默认值固定为 `128 MiB`;用户配置的正值
+会覆盖默认值。显式的非正配置无效,应在创建运行时时拒绝。预算不从输入大小派生,流预算
+也不应依赖动态已读字节核算。
+
+对象图预算核算应:
+
+- 在顶层读取状态中初始化,并由顶层反序列化 `finally` 负责清理;
+- 只核算 Fory 创建并由返回值对象图保留的对象或存储;仅在构造期间使用的临时辅助对象
+ 不属于对象图预算;
+- 不声称能够预算显式策略允许相应代码后执行的任意构造函数、可调用对象、描述符、
+ finalizer 或状态恢复内部资源;
+- 将读取上下文/读取状态限制为原始字节预留;计数运算以及集合、map、数组、结构体和对象
+ 存储公式属于具体序列化器或生成序列化器所有者;
+- 在比较预算或分配前拒绝算术溢出;
+- 估算浅层所有者存储的下限:引用支持或堆实体化的集合、map、set 和引用数组预留非零
+ 浅层自身成本加上后备/引用/内联存储;引用支持或堆实体化的 struct、record、POJO、
+ tuple/product、兼容、生成和动态对象所有者预留非零浅层自身成本加上浅层字段存储;
+- 当实际引用槽位大小无法低成本或可靠查询时,使用 4 字节引用槽位;内联存储使用基本
+ 类型/值字段宽度;
+- 在分配后备存储或预留容量前保留现有字节可用性检查;
+- 跳过作为独立所有者的 enum/union,并跳过专用的 string、binary、基本类型标量、基本
+ 类型数组和基本类型稠密数组叶子所有者,除非运行时特有所有者章节明确将其纳入。
+
+被跳过的叶子所有者仍必须受剩余输入字节限制。如果未读输入没有足够字节表示 string、
+binary 值、基本类型标量、基本类型数组或基本类型稠密数组,运行时不得读取或创建该叶子值。
+
+每个运行时都必须在选择公式前检查具体所有者路径。在存储、装箱或分配值的所有者处恰好
+预留一次自身存储。反序列化 facade 可以为每次操作重置预算,但不得预留顶层结果类型、
+自身字节或值存储。引用支持的路径预留父所有者自身成本加上引用存储,而每个被引用的堆
+所有者在实体化时预留自己的浅层自身成本。内联/值路径在持有者/分配所有者中预留内联
+元素、字段或装箱存储;顶层值序列化器和生成的 struct/product 读取路径不得计算自身存储。
+对于内联/值集合或 map 运行时,反序列化 facade 或容器序列化器不得仅仅因为顶层值容器
+是返回值就对其计费。嵌套值容器作为父持有者的内联槽位,或作为实际拥有这些槽位的外层
+集合后备存储元素计费。指针、box、智能指针或类型擦除实体化路径为其分配的堆值预留浅层
+存储。父所有者不得递归包含子对象、集合、map、string、binary 或基本类型稠密数组的内容;
+子所有者在实体化时预留自己的浅层内存。
+
+#### Java Fory 核心
+
+Java Fory 核心基本类型数组序列化器预留可移植数组头部,加上逻辑长度乘以基本类型存储
+宽度。基本类型 list 序列化器预留返回 list 的浅层所有者、后备数组头部以及相同的基本
+类型存储。这些已知长度路径在现有成比例可读字节检查后、分配前预留一次;它们不使用
+增量批次。压缩输入使用解压后的逻辑长度,而临时压缩数组仍是保留对象图预算之外的构造
+暂存。Float16 和 BFloat16 稠密数组载体还包括其包装器的浅层所有者。当装箱 list 转换
+首先解码基本类型数组时,数组预留仍作为最终 list 估算的抵扣,转换只预留正的剩余差值。
+
+#### 生成的结构化目标
+
+编码成员和物理存储是不同的输入。属性、访问器、接口和逻辑 Schema 别名不是物理字段,
+不得作为存储计费。既序列化又存储的字段只计数一次。仅存储声明贡献其字段宽度,但不得
+进入编码元数据或生成的读写操作。
+
+对于 C# 普通类,每个直接带注解的类拥有该类声明的物理实例字段。可继承类提供方发布
+累计父提供方值加上这些直接字段。密封具体序列化器在内部使用相同累计表达式。具体后代
+使用可立即访问的提供方值及其自身直接字段;不得枚举被引用的私有元数据或重建父存储。
+具体对象序列化器预留一个浅层对象所有者加上此累计字段存储。
+
+C# 外部类声明拥有其列出的确切第三方物理字段。确切字段映射贡献存储。可见属性映射不
+贡献存储,因此其后备字段必须单独列出。忽略的映射必须标识一个确切类字段,且仅用于
+存储。外部 struct 声明仅支持可见成员映射。可发现但未映射的公共实例字段可以添加一次。
+`BaseOnly` 声明可以拥有普通子类使用的完整目标和目标祖先前缀。它必须列出该前缀中的
+每个非公共物理字段;生成器不会扫描被引用 assembly 的私有布局。
+
+确切外部私有身份是固定到版本的软件包 ABI 断言。运行时编码访问使用确切访问器,不得
+回退到反射、布局探测或其他成员。仅存储的私有声明没有运行时访问器,因此应用必须根据
+固定的软件包版本验证它们。
+
+Dart 生成器还可以包含生成时在目标上可见的公共实例字段。Swift 宏无法检查其他类型的
+存储布局,因此只使用外部声明。在每个运行时中,这些公式都在生成期间解析,不得向
+反序列化热路径添加反射、布局探测、分配或字段枚举。常规所有者规则仍然适用:引用目标
+预留其浅层所有者和字段存储,而内联值目标由拥有其存储的持有者计费。
+
+#### 运行时特有所有者说明
+
+##### C++
+
+C++ 普通 struct、product 和标准库容器属于值存储,除非指针、智能指针或类型擦除所有者
+在堆上分配它们。顶层反序列化会初始化剩余对象图预算,但不会为返回值预留 `sizeof(T)`。
+普通值序列化器不得仅仅因为正在读取值,就预留自身的 `sizeof(T)`。
+
+泛型集合和 map 序列化器预留容器路径拥有的元素、键和值存储下限。嵌套值容器头部作为
+父对象的内联槽位或外层容器后备存储中的元素时计费。智能指针和类型擦除实体化路径在
+发布或返回其分配的堆值前,为该值预留浅层存储。泛型 C++ 路径不得臆造标准库头部、
+节点、桶、分配器或调试布局开销。
+
+##### Rust
+
+Rust struct、tuple、enum 和集合值属于内联值存储,除非由 `Box`、`Rc`、`Arc` 或类型擦除
+所有者分配。顶层和派生值读取路径初始化或消耗预算,但不为正在读取的值预留
+`size_of::<Self>()`。`Vec`、`HashMap`、`BTreeMap` 及类似序列化器预留其根据计数分配的
+后备或条目值存储;嵌套值容器头部作为父内联字段或外层后备元素计费。
+
+装箱、引用计数和类型擦除实体化路径为其创建的堆载荷预留 `size_of::<T>()`。这些分配
+所有者可以使用编译期 `size_of::<T>()` 公式,但值序列化器不应为同一个 `T` 添加并行
+自身预留。
+
+在根据计数分配前,如果 Rust 所有者的确切重复操作已证明至少消耗一个字节,则保留完整
+可读字节关卡。不确定的所有者只需对计数中未被剩余无输入支撑条目余量覆盖的部分要求
+可读字节。在分配所有者处恰好应用一次所选关卡;读取共享元数据后不要重复。写入端继续
+编码合法的紧凑或空主体,不执行此读取端余量。
+
+固定数组不根据经过验证的编码计数进行分配,因此省略分配关卡。对于零大小元素,`Vec`、
+`VecDeque` 和 `BinaryHeap` 也省略该关卡,因为在这种情况下它们不会创建根据计数派生的
+后备分配。在声明计数驱动分配时,节点、桶和条目所有者保留该关卡。实现不得以猜测的
+分配成本、填充字节、全局紧凑主体绕过或第二个集合/map codec 取而代之。
+
+##### Swift
+
+Swift struct、enum、tuple 和集合值属于值存储。顶层值读取和嵌套值序列化器不应预留
+自身存储。拥有该值的持有者(例如 struct 字段、数组后备存储、dictionary 条目存储或
+装箱/动态实体化路径)拥有相应的对象图预算预留。
+
+数组、dictionary 和 set 序列化器分配或预留后备存储时,可以使用稳定的 Swift 类型大小
+信息(例如 `MemoryLayout<T>.stride`)预留后备存储下限。当 Fory 创建保留的对象或 box
+时,类、existential 或装箱实体化路径预留所有者存储。不应向读取热路径添加运行时对象
+布局探测。
+
+##### Go
+
+Go struct 以及 slice 或 map 头部属于值存储,除非由指针、接口实体化或其他堆所有者分配。
+顶层反序列化和 struct 值序列化器不应自行预留返回的 struct 或嵌套内联 struct。指针
+序列化器在分配保留的 `*T` 时预留具体 struct 存储。
+
+Slice、array、map 和 set 序列化器预留其根据声明计数分配的后备或条目存储。元素和条目
+宽度应尽可能来自序列化器或解析器捕获的稳定类型信息;如果所有者已经知道具体类型,读取
+循环不应重新计算反射大小信息。接口或动态路径只预留
+Fory 明确实体化并保留的存储。
+
+##### C\#
+
+C# 同时包含引用所有者和内联值类型。类、数组、list、dictionary、hash set 和其他堆容器
+预留非零浅层所有者成本,加上直接后备、引用槽位或内联元素存储。即使 dictionary 的键
+或值类型是 struct,dictionary 也是引用类型容器,因此 dictionary 所有者仍与其条目存储
+分开计费。
+
+值 struct 内联读取时不预留自身存储;存储该 struct 的持有者(例如对象字段、数组元素、
+list 后备存储、dictionary 条目、box 或动态实体化路径)拥有该预留。当 Fory 创建保留的
+box 时,装箱、`object` 和动态实体化路径预留一个装箱所有者。所有者常量应是相关 C#
+对象或容器形状真正可移植的下限,而不是占位标记。
+
+运行时不应猜测对象头部、数组头部、分配器头部、调试模式字段、hash 桶、树链接、hash 链
+链接、节点头部、map 条目对象、备用块或运行时表布局,除非所有者路径具有低成本、稳定、
+显式的存储下限信号并记录了公式。所有者常量应是所有者形状的真实下限,而不是占位标记。
+
+### 跳过语义
+
+跳过未知或不兼容数据时,应根据具体影响进行分类,而不是根据运行时是否实体化临时值。
+
+当直接消耗编码内容很简单且由当前运行时路径负责时,这样做很有用。对于 list、set 和 map
+等复杂字段,这不是安全要求。如果能够保留现有序列化器所有权模型,运行时可以实体化值
+然后将其丢弃。
+
+对于扩展、动态或用户拥有的类型,所属运行时可能不总是有足够信息在不调用已注册序列化器
+的情况下跳过。此时应根据具体影响分类行为:
+
+- 资源泄漏、保留状态、无进度循环或策略绕过与安全相关。
+- 允许有界实体化后返回错误或丢弃,除非它造成显著内存或 CPU 压力。
+- 仅要求被跳过值使用某一种特定编码形状的严格性,不是安全问题。
+
+### 元数据与类型解析
+
+当元数据解析影响保留的读取端状态、类型分派或策略决策时,它对安全敏感。
+
+元数据读取器应:
+
+- 避免嵌套元数据结构中的无界递归。
+- 避免攻击者控制的元数据流导致表无界增长。
+- 在使用元数据主体绕过或替换现有策略决策前验证它们。
+- 对于 Java 元数据路径,将 `TypeChecker` 等名称级检查和禁止类列表保持在
+ `Class.forName` 之前;为此,通过现有 `TypeResolver.loadClass` 所有者路由远端类名加载。不要通过直接从
+ TypeDef 或 TypeMeta 名称加载类来绕过该所有者。被拒绝的输入名称不得导致类加载。
+ 将此决策移动到加载前时,应保留注册、动态加载和未知类型语义。需要实体化
+ `Class<?>` 的检查仍在加载后执行;不要用仅基于字符串的近似检查替代它们。
+- 将完整的输入数组描述符传给 `TypeChecker`。输入最多可从已接受组件类派生六维数组。
+ 更高维数组要求精确可信的完整数组注册或经过检查的名称缓存条目,使输入无法让 JVM
+ 派生无界的数组类系列。
+- 在正确的根操作边界重置或释放元数据状态。
+
+不可信反序列化可以访问的类解析缓存,只能从显式可信配置发布条目,或在当前类策略接受
+解析出的类后发布条目。因此,缓存命中表示已经可信且验证过的 `Class<?>`,应直接使用
+缓存类,而不重复类加载或名称级 `TypeChecker` 工作。只有缓存未命中才执行这些名称级
+检查并发布接受的结果。需要实体化 `Class<?>` 的检查仍由其现有调用方负责。存储纯数据
+未知类占位符的缓存条目可以在精确命中时返回同一个占位符,但不得授权加载最初未命中的
+编码名称。对于 ID 注册和名称注册,精确的已注册名称表命中都是可信的;经过检查的名称
+缓存精确命中也是可信的。两个精确查找都未命中后,读取器不得从反向注册、以类为键的
+状态或 `Class.getName()` 推断另一个已接受名称。自定义名称注册本身不会将 Java 类名发布
+为额外别名;ID 注册会发布 Java 类名。
+
+能够创建持久读取状态的远端元数据,必须在保留该状态前受到限制。该检查仅用于资源控制:
+不得改变编码兼容性、类型注册、动态类加载、未知类型处理、反序列化策略或 Schema 演进
+语义。失败或不兼容的元数据不得消耗 Schema 版本限制;元数据缓存命中或生成的字段读取器
+不得为这些限制添加验证、hash、分配或策略工作。元数据解析、缓存发布、精确本地匹配和
+计数的具体顺序属于 [xlang 实现指南](../specification/xlang_implementation_guide.md)。
+
+经过检查的元数据缓存,是判断接收的 TypeDef 或 TypeMeta 头部是否已经验证的唯一所有者。
+元数据缓存命中意味着该头部先前已被解析、通过主体/hash 验证和策略检查,并由所属缓存
+发布,因此读取器必须跳过剩余元数据主体并使用缓存元数据,不重复主体验证、hash 验证、
+限制检查、精确本地检查或策略工作。元数据缓存未命中是唯一会解析元数据主体、验证其
+hash 和形状、执行元数据限制、进行精确本地字节比较并发布到缓存的路径。不要添加单独的
+可空标志、哨兵头部、每 TypeInfo 接受标记或并行状态来表示此决策。
+
+只有实际作为 TypeDef 或 TypeMeta 主体携带的元数据才受元数据主体和 Schema 版本限制。
+兼容的命名 enum、ext 和 union 元数据通常只有一个版本,但当它作为共享元数据发送时,
+仍计入远端元数据总量限制。纯基于 ID 的 enum、ext 和 typed-union 值使用 type id 加 user
+type id,不得移到此元数据主体路径。
+
+远端元数据主体和 struct 字段列表也必须在冷元数据解析路径上受到限制。
+`maxTypeMetaBytes` 限制一个接收的 TypeDef 或 TypeMeta 主体中的编码元数据主体字节数,
+不包括 8 字节头部和任何扩展大小 varint。`maxTypeFields` 限制一个接收的 struct 元数据
+主体声明的字段数量。对于 Java 原生 TypeDef 类层级,字段限制适用于该 TypeDef 所有类
+层级的字段总数。这些限制在根据攻击者声明的元数据大小或字段计数进行复制、解压、预留
+或分配之前检查。
+
+默认限制为 `maxTypeFields = 512` 和 `maxTypeMetaBytes = 4096`。运行时应将限制失败报告为
+可能的恶意数据,并告知用户只有在数据没有恶意时才提高确切选项。这些限制不得在元数据
+缓存命中、生成的序列化器或已解析 type-id 热路径上引入验证。
+
+单纯的元数据字节形式严格性不是安全要求。只有当所有者需要这种严格性,或该形状会改变
+类型身份、保留状态、资源使用或策略行为时,拒绝元数据形状才有意义。
+
+### 引用跟踪
+
+引用跟踪是编码协议的一部分,并且对性能敏感。读取器可以使用哨兵值和共享的携带值分支,
+以保持热路径紧凑。
+
+当格式错误输入能够导致以下结果时,引用跟踪验证与安全相关:
+
+- 访问越界引用而不报告错误。
+- 根操作失败后留下保留的引用状态。
+- 在引用值可用之前注册无界回调或解析器状态。
+- 导致无进度循环或崩溃。
+
+不能仅仅因为格式错误的标志没有在最早可能的字节处被拒绝,就要求进行引用跟踪验证。
+只要根操作仍返回错误且没有违反安全不变量,延迟拒绝就是可接受的。下游错误无需是专用
+引用协议错误。
+
+### 错误传播与清理
+
+Fory 运行时可以有意使用延迟错误传播。读取记录错误后,后续读取步骤可以继续,直到外层
+操作观察到并返回该错误。
+
+如果继续执行的工作不会导致以下结果,则可以接受:
+
+- 崩溃或 panic。
+- 分配或保留攻击者控制的状态。
+- 泄漏资源。
+- 绕过必需的清理。
+- 跨越显式验证或策略边界返回成功。
+
+只有当外层根操作清理无法覆盖嵌套路径拥有的状态或资源时,才应添加嵌套
+`try`/`finally` 或等价清理。
+
+### 性能要求
+
+安全验证必须保持 Fory 热路径性能。如果验证仅用于严格性且会引入以下内容,请勿添加:
+
+- 每元素对象分配。
+- 热循环中的动态分派或回调。
+- 成功路径上的包装对象或结果载体。
+- 缓冲区支持的 string、binary 或基本类型数组读取所需的额外复制。
+- 无法保护安全不变量的分支。
+- 唯一目的只是统一最终错误类型、消息、位置或时机的辅助调用或生成代码膨胀。
+
+优先使用可以内联、且已经利用当前序列化器可用信息的所有者局部检查。不要将序列化器
+拥有的语义移入通用读取上下文辅助方法。
+
+### 分类指南
+
+审查反序列化行为时,请使用以下问题:
+
+1. 此输入能否导致崩溃、panic 或越界内存访问?
+2. 较小或未经证明的输入长度能否导致不成比例的分配?
+3. 流支持的读取器能否在精确读取或跳过证明字节存在之前增长缓冲区?
+4. 循环能否在没有字节进度或逻辑进度的情况下继续?
+5. 根操作失败后,该路径能否保留攻击者控制的状态?
+6. 该路径能否泄漏资源或跳过必需的清理?
+7. 该路径能否跨越显式 Fory 策略边界返回成功?
+8. 所提议的验证在相关热路径上是否几乎没有成本?
+
+如果前七个问题的答案都是否,该问题通常不属于安全问题。如果验证并非几乎没有成本,
+除非协议所有者明确要求,否则请避免添加。
+
+### 文档边界
+
+安全模型文档不得包含利用样例、CVE 叙述、行级潜在漏洞、分支历史、迁移时间线或清理计划。
+请根据需要将这些细节保留在私有报告、issue 或 pull request 中。
+
+公开安全文档应描述持久边界和不变量,而不是实现如何达到这些状态的历史。
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org