pjfanning opened a new pull request, #3503:
URL: https://github.com/apache/pekko/pull/3503

   ### Motivation
   Four issues in `JacksonSerializer`, all reachable from the manifest string 
on the wire.
   
   **The case object branch resolved the class in a way that initializes it, 
before checking
   the allow list.** When the manifest ends in `$`, `fromBinary` called
   `system.dynamicAccess.getObjectFor(className)`, which reads the `MODULE$` 
field through a
   `VarHandle` and so runs the class's initializer; `checkAllowedClass` ran 
only afterwards.
   The only gate before that point is `checkAllowedClassName`, which consults 
Jackson's gadget
   deny list rather than `serialization-bindings` or `allowed-class-prefix`. So 
a manifest
   naming any Scala `object` on the classpath ran that object's body on the way 
to being
   rejected. The sibling branch already does the right thing — `getClassFor` is
   `Class.forName(fqcn, false, cl)` and does not initialize. Same shape as 
#3495.
   
   **`LZ4Meta.get` checked for four remaining bytes and then read eight** — the 
magic plus the
   declared length. A 4-to-7 byte payload beginning with `0x87D96DF6` raised
   `BufferUnderflowException` out of `decompress`.
   
   **`parseManifest` called `toInt`** on whatever followed the last `#`, so 
`Foo#abc` or a
   trailing `#` raised `NumberFormatException` rather than a serialization 
error.
   
   **`isInAllowList` evaluated the throwing operand first.** 
`isBoundToJacksonSerializer` calls
   `serializerFor`, which raises `NotSerializableException` — stack trace and 
all — for a class
   that is not bound, and that is exactly the case for a class allowed only by
   `allowed-class-prefix`. `checkAllowedClass` runs on every `fromBinary`, so 
this built and
   discarded one exception per message on that path.
   
   ### Modification
   - Resolve the case object's class with `getClassFor`, run 
`checkAllowedClass` on it, and read
     the module field only after that.
   - Require 8 remaining bytes before reading an LZ4 header.
   - Parse the manifest version with `toIntOption` and report a non-numeric one 
as
     `NotSerializableException` naming the manifest.
   - Test `hasAllowedClassPrefix` before `isBoundToJacksonSerializer`.
   
   Applied identically to `serialization-jackson` and `serialization-jackson3`.
   
   The reordering in `isInAllowList` is the only change with no behaviour 
difference — both
   operands are pure predicates and `isBoundToJacksonSerializer` already 
swallows its exception —
   so it is covered by the existing `allowed-class-prefix` tests rather than a 
new one.
   
   ### Result
   A rejected manifest no longer initializes the class it names; two malformed 
manifests are
   reported as serialization failures instead of unrelated runtime exceptions; 
the allow list
   check no longer constructs an exception per message on the prefix path. No 
change for
   manifests that were accepted before.
   
   ### Tests
   New tests in `JacksonSerializerSpec` in both modules (so each runs under 
both the JSON and
   CBOR serializers):
   
   - `not allow deserialization of a case object that is not in 
serialization-bindings` — the
     allow list still rejects it
   - `not initialize a case object class it goes on to reject` — a test-only 
object whose body
     sets a flag; the flag must still be false after the rejection
   - `reject a manifest whose version is not a number`
   - `not underflow on a payload that is only as long as the LZ4 magic`
   
   I checked these discriminate by reverting the production change and 
re-running: the last
   three fail without it (`true did not equal false`, `NumberFormatException 
was thrown`, and
   the underflow respectively). The first passes either way — it documents that 
the allow list
   decision itself is unchanged, which is the point.
   
   - `sbt "serialization-jackson/testOnly 
org.apache.pekko.serialization.jackson.*"` — 128 passed
   - `sbt "serialization-jackson3/testOnly 
org.apache.pekko.serialization.jackson3.*"` — 126 passed
   - `sbt "serialization-jackson/mimaReportBinaryIssues"` — no issues 
(`serialization-jackson3`
     disables MimaPlugin)
   - `sbt scalafmtAll headerCreateAll` — no changes
   
   ### References
   The case object issue is the same shape as #3495, which stopped the 
serializer resolving a
   wire-supplied manifest class it would not use.
   
   `ProtobufSerializer.isInAllowList` has the same throwing-operand-first 
ordering, but it
   caches its method handle after the check so it pays the cost once per class 
rather than once
   per message; it is in `pekko-remote` and is left for a separate change.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to