Hi Peter,

Indeed, the ability to limit the types permitted to be loaded (contextually to what is unmarshalled) is important to minimize unmarshalling-associated risks. In my work on Marshalling I currently rely on an allow-list (essentially String -> Class, as I currently find ClassLoader to have a rather large API surface for this purpose) since parsing a schema descriptor is resolving types. For schemaless payloads, the schema needs to be either known in advance, or be determinable from the shape of the data itself.

Current designs of Marshalling are essentially T -> record -> output and input -> record -> T where the record serves both as schema definition (using the record components as both names and types are present at runtime). This encoding also permits versioning (structure-as-version) as well as translation (record-to-enum-value, record-to-cache-lookup, or equivalent).

>We don't think of the object's serial form as serialised fields; they are serialised parameter arguments used to create new objects.

That resonates with how I view it as well.

On 2026-08-27 07:51, Peter Firmstone wrote:
Hi Victor,

We migrated away from Java Serialisation in stages, admittedly it was a significant task, but after watching your video, I thought I'd share some of my experiences.

The first step we made was to create a deserialiser that used constructors, serialisation still used OOS, we reimplemented OIS, I retained the existing object serial form.  I posted about it here previously: Explicit Serialization API and Security <https://mail.openjdk.org/pipermail/core-libs-dev/2015-January/030724.html> Source:AtomicSerial xref <https://pfirmstone.github.io/JGDMS/xref/org/apache/river/api/io/AtomicSerial.html>

I built a framework around deserialisation and tools to defensively validate invariants prior to object construction, it didn't support circular object graphs.

For the second stage, I implemented OOS and serialisers for Java Collections and classes we needed but couldn't control, eg Java platform classes.  One of the decisions I made in the design, was Collections were immutable, and designed to be copied during invariant validation, there's an important reason for this I will focus on later.

The API was originally a constructor signature with an annotation, it grew to include two static methods for the developer to declare class  schema and serialise the object.

The constructor signature is standardised with a caller sensitive parameter, allowing implementations to retrieve parameter arguments, but only for the calling class's private namespace. This parameter argument can then be passed to a super class constructor.   This constructor signature is designed for class inheritance hierarchy evolution and refactoring, including changes in implementations from inheritance to encapsulation, and intra class invariant checks.

We don't think of the object's serial form as serialised fields; they are serialised parameter arguments used to create new objects.

The third stage was to implement a new serialisation wire protocol.

One of the issues we have is our security model is designed to safely run foreign code, audited using static analysis, but untrusted, using SM with PoLP generated policy, but OpenJDK's is now based on trusted code, having since removed SM, for now we've forked OpenJDK while we try to reduce our dependency on dynamic code, inherited from its Jini origins.

JERI - Jini Extensible Remote Invocation, this is similar to other remote invocation frameworks like RMI, but unlike RMI it's extensible, it has multiple layers, each with pluggable implementations, which allowed Java Serialisation to be replaced.

Something that may not be obvious, is class resolution during deserialisation.  Mike Warres's 2006 paper "Class Loading Issues in Java™ RMI and Jini™ Network Technology", section 6.2/6.3, to pry apart the multiple functions ClassLoaders conflate, we implemented a per-proxy dedicated ClassLoader model, which is a working instance of this separation, basically we replaced the duplication of class resolution that RMIClassLoader created and instead assigned a ClassLoader to the stream, that determines class visibility.  This has been a very clean solution to class resolution.

Now about the new serialisation protocol, Java Serialization is opaque and non deterministic, and JavaSpaces used it as though it were deterministic, serial form was used for equality in JavaSpaces, to avoid deserialization, creating issues with equality due to non determinism.

For our serialisation protocol we chose canonical DER ASN.1 encoding, and we developed rules around collections to ensure determinism, such that we have cross platform collections, with identical serial form.  This allows us to make equality comparisons of collections in Java Spaces, no matter which programming language the object originated from.  Objects are serialised with their schema, this is checked at parsing, which is performed defensively.  Schema is not designed for evolution, it is exact and specific, any differences between schema and serialised objects is exceptional and will throw.

To reduce the dependency on dynamic code, we are implementing Turing-incomplete remote functions.  These functions are incapable of arbitrary side effects; no reflection, no arbitrary method dispatch, nothing beyond an expression grammar which includes a subset of CEL syntax and mathematical operations, that will allow JavaSpace searches to use logical comparison and simple calculations.

Furthermore the schema and DER encoding are fully transparent, parameters can be deeply introspected back to their string and primitive values, providing fodder for functions.

--
Cheers,
√


Viktor Klang
Software Architect, Java Platform Group
Oracle

Reply via email to