8000 [#1355]Fix: reject non-Metric types when deserializing algorithm metrics by NopeCharon · Pull Request #1369 · Angel-ML/angel · GitHub
[go: up one dir, main page]

Skip to content

[#1355]Fix: reject non-Metric types when deserializing algorithm metrics - #1369

Open
NopeCharon wants to merge 1 commit into
Angel-ML:branch-3.4.0from
NopeCharon:fix#1355
Open

NopeCharon wants to merge 1 commit into
Angel-ML:branch-3.4.0from
NopeCharon:fix#1355

Conversation

@NopeCharon
Copy link
Copy Markdown

Pre-submission checklist:

Please check each of these after submitting your pull request:

  • Do you submit an issue before submitting your pull request, each pull request is supposed to connect to an issue. If your do not submit an issue, please submit one at first.
  • Do you connect you pull request to the issue. The title of pull request should start with [Issue ID]
  • Do you assign a code reviewer? If your don't know how to select code reviewers, please refer CODEOWNERS
  • If you pull request is big, it would be hard to merge by the community at once, please contact the TSC member in advance.

What this PR does

Fixes the deserialization part of #1355.

MasterService.setAlgoMetrics hands the bytes from the RPC request to KryoUtils.deserializeAlgoMetric, which called readClassAndObject and cast the result to Metric only afterwards. Two things followed from that:

  • any class named in the payload was instantiated and its fields populated before the cast failed;
  • a forged primitive array header (for example long[] with a length near Integer.MAX_VALUE) made the Master allocate the whole array before reading any element, and the OutOfMemoryError took down the Master.

deserializeAlgoMetric now reads the class header on its own, checks that it is a Metric subclass, and only then reads the object with readObject. Anything else is rejected with a KryoException before an object is created. The check is by type, not a fixed list of LossMetric/ErrorMetric/ObjMetric, so user-defined Metric subclasses keep working.

Because the class and the object are now read in two calls, auto reset is turned off on the thread-local KryoReflectionFactorySupport used here and the graph state is reset in a finally block once the metric has been read. Without this, Kryo would clear its class name table right after readClass, and a metric whose fields refer back to its own class would fail with Unable to find class.

The wire format is unchanged, so the serializing side (MasterClient) is untouched.

Not covered here: the RPC endpoint itself is still unauthenticated. The issue also suggests adding authentication, which is a much larger design change and is better handled separately.

Tests

New KryoUtilsTest (JUnit 4, same as the other core tests):

  • round trip of LossMetric, ErrorMetric and ObjMetric
  • round trip of a null metric
  • a custom metric with a field of its own class, read twice to show the graph state is reset between calls
  • a non-Metric object is rejected with KryoException
  • a forged long[] header with a huge length is rejected with KryoException

How I ran them: I compiled KryoUtils and the test with JDK 8 against the pinned kryo-shaded 4.0.0 and kryo-serializers 0.42 jars, using the released angel-ps-core 3.2.0 jar for the Metric classes. I couldn't run the full Maven build because protoc 2.5.0 wasn't available on my build machine.

  • With this change: all 5 tests pass.
  • With the original deserializeAlgoMetric: the two rejection tests fail with the behaviour described in the issue (ClassCastException for the non-metric object, OutOfMemoryError: Requested array size exceeds VM limit for the forged array).
  • With the type check but without the reset handling: the self-referencing metric test fails.
A33D

…thm metrics

MasterService.setAlgoMetrics passes the bytes received over RPC to
KryoUtils.deserializeAlgoMetric, which called readClassAndObject and only
cast the result to Metric afterwards. Any class named in the payload was
therefore instantiated and populated first, and a forged primitive array
header with a huge length made the Master allocate the whole array before
reading a single element, crashing it with an OutOfMemoryError.

Read the class header on its own and check that it is a Metric subclass
before reading the object, throwing a KryoException otherwise. Custom
Metric subclasses keep working because the check is by type rather than
a fixed list of the bundled metrics.

Since the class and the object are now read in two calls, auto reset is
disabled on the thread-local Kryo used for deserialization and the graph
state is reset once the whole metric has been read. Otherwise class name
references inside the metric (for example a field of the metric's own
class) could not be resolved.

Add KryoUtilsTest covering round trips of the bundled metrics, a null
metric, a metric that references its own class, and rejection of a
non-metric object and of a forged long[] header.

Signed-off-by: NopeCharon <115794254+NopeCharon@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0