From eb93beeaa9d4fba96cb948d8ae194f75fe5c7cc0 Mon Sep 17 00:00:00 2001 From: nishat shabbir Date: Tue, 18 Aug 2026 17:34:46 +0530 Subject: [PATCH 1/2] YARN: restrict leveldb assigned-resources deserialization to known types --- .../container/ResourceMappings.java | 23 ++++++++++-- .../container/TestResourceMappings.java | 37 +++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/ResourceMappings.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/ResourceMappings.java index c1c3b5d0aa792e..c33ce0116036b4 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/ResourceMappings.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/ResourceMappings.java @@ -18,9 +18,11 @@ package org.apache.hadoop.yarn.server.nodemanager.containermanager.container; +import org.apache.commons.io.serialization.ValidatingObjectInputStream; import org.apache.commons.lang3.SerializationException; import org.apache.commons.lang3.SerializationUtils; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; @@ -89,9 +91,24 @@ public void updateAssignedResources(List list) { public static AssignedResources fromBytes(byte[] bytes) throws IOException { final List resources; - try { - resources = SerializationUtils.deserialize(bytes); - } catch (SerializationException e) { + // The bytes come from the NM recovery state store and are read back + // during container recovery on restart. Deserialize through a + // ValidatingObjectInputStream so a tampered record cannot instantiate + // arbitrary serializable classes on the NodeManager classpath. The + // allowed graph is the assigned-resource value objects the resource + // plugins store (device / NUMA descriptors, plain strings) plus the + // collection types that wrap them. + try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + ValidatingObjectInputStream ois = + new ValidatingObjectInputStream(bais)) { + ois.accept( + "org.apache.hadoop.yarn.server.nodemanager.*", + "org.apache.hadoop.thirdparty.com.google.common.collect.*", + "java.util.*", + "java.lang.*", + "[Ljava.lang.Object;"); + resources = (List) ois.readObject(); + } catch (ClassNotFoundException e) { throw new IOException(e); } AssignedResources ar = new AssignedResources(); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/TestResourceMappings.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/TestResourceMappings.java index 02cc63f44cc23c..40628d33ee400d 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/TestResourceMappings.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/TestResourceMappings.java @@ -19,16 +19,21 @@ import org.apache.hadoop.thirdparty.com.google.common.collect.ImmutableList; import org.apache.hadoop.yarn.server.nodemanager.api.deviceplugin.Device; +import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.resources.numa.NumaResourceAllocation; +import org.apache.hadoop.yarn.server.nodemanager.containermanager.resourceplugin.fpga.FpgaDevice; +import org.apache.hadoop.yarn.server.nodemanager.containermanager.resourceplugin.gpu.GpuDevice; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; public class TestResourceMappings { @@ -94,6 +99,38 @@ public void testAssignedResourcesCanDeserializePreviouslySerializedValues() { } } + @Test + public void testRoundTripCoversResourcePluginTypes() throws IOException { + // The elements a NodeManager actually stores for gpu / fpga / numa + // resources must survive the allowlist, otherwise recovery would break. + ResourceMappings.AssignedResources pluginResources = + new ResourceMappings.AssignedResources(); + pluginResources.updateAssignedResources(ImmutableList.of( + new GpuDevice(2, 3), + new FpgaDevice("IntelOpenCL", 247, 0, "aclv0"), + new NumaResourceAllocation("0", 1024L, "0", 4), + "cpu-0")); + + ResourceMappings.AssignedResources deserialized = + ResourceMappings.AssignedResources.fromBytes(pluginResources.toBytes()); + + assertEquals(pluginResources.getAssignedResources(), + deserialized.getAssignedResources()); + } + + @Test + public void testFromBytesRejectsUnexpectedType() throws IOException { + // A tampered record whose top-level list is fine but which carries an + // element of a type the resource plugins never store. This stands in for a + // serialization gadget (e.g. a commons-beanutils BeanComparator): the + // allowlist rejects it by class name during readObject, before the class + // is loaded or any of its logic runs. + byte[] payload = toBytes(ImmutableList.of( + new File("/etc/passwd"))); + assertThrows(IOException.class, + () -> ResourceMappings.AssignedResources.fromBytes(payload)); + } + /** * This was the legacy way to serialize resources. This is here for * backward compatibility to ensure that after YARN-9128 we can still From 44f90149d6a99cd7471e13dbf2444cd5d8d653f2 Mon Sep 17 00:00:00 2001 From: nishat shabbir Date: Thu, 20 Aug 2026 13:17:46 +0530 Subject: [PATCH 2/2] YARN: tighten assigned-resources allowlist to concrete types and fail closed on non-list records Signed-off-by: nishat shabbir --- .../container/ResourceMappings.java | 33 ++++++++++++++----- .../container/TestResourceMappings.java | 4 +-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/ResourceMappings.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/ResourceMappings.java index c33ce0116036b4..ca220bc0d0786d 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/ResourceMappings.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/ResourceMappings.java @@ -21,6 +21,10 @@ import org.apache.commons.io.serialization.ValidatingObjectInputStream; import org.apache.commons.lang3.SerializationException; import org.apache.commons.lang3.SerializationUtils; +import org.apache.hadoop.yarn.server.nodemanager.api.deviceplugin.Device; +import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.resources.numa.NumaResourceAllocation; +import org.apache.hadoop.yarn.server.nodemanager.containermanager.resourceplugin.fpga.FpgaDevice; +import org.apache.hadoop.yarn.server.nodemanager.containermanager.resourceplugin.gpu.GpuDevice; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -94,20 +98,33 @@ public static AssignedResources fromBytes(byte[] bytes) // The bytes come from the NM recovery state store and are read back // during container recovery on restart. Deserialize through a // ValidatingObjectInputStream so a tampered record cannot instantiate - // arbitrary serializable classes on the NodeManager classpath. The - // allowed graph is the assigned-resource value objects the resource - // plugins store (device / NUMA descriptors, plain strings) plus the - // collection types that wrap them. + // arbitrary serializable classes on the NodeManager classpath. try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ValidatingObjectInputStream ois = new ValidatingObjectInputStream(bais)) { + // The concrete value objects the resource plugins store, the list + // types that wrap them (a fresh ArrayList, or the unmodifiable view + // that legacy records were serialized from), and the strings / boxed + // primitives those objects hold. Number is the superclass descriptor + // read back for the boxed Integer / Long map values. + ois.accept(ArrayList.class, String.class, Number.class, + Integer.class, Long.class, Device.class, GpuDevice.class, + FpgaDevice.class, NumaResourceAllocation.class); + ois.accept("java.util.Collections$UnmodifiableList", + "java.util.Collections$UnmodifiableCollection"); + // NumaResourceAllocation serializes its shaded-guava ImmutableMaps + // through guava's internal SerializedForm, which carries the keys and + // values in an Object[]. The forms are a guava-internal detail, so the + // collect package is matched by pattern rather than pinned class name. ois.accept( - "org.apache.hadoop.yarn.server.nodemanager.*", "org.apache.hadoop.thirdparty.com.google.common.collect.*", - "java.util.*", - "java.lang.*", "[Ljava.lang.Object;"); - resources = (List) ois.readObject(); + Object obj = ois.readObject(); + if (!(obj instanceof List)) { + throw new IOException("Unexpected assigned-resources record type: " + + (obj == null ? "null" : obj.getClass().getName())); + } + resources = (List) obj; } catch (ClassNotFoundException e) { throw new IOException(e); } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/TestResourceMappings.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/TestResourceMappings.java index 40628d33ee400d..bbc49ea2b39697 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/TestResourceMappings.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/container/TestResourceMappings.java @@ -123,8 +123,8 @@ public void testFromBytesRejectsUnexpectedType() throws IOException { // A tampered record whose top-level list is fine but which carries an // element of a type the resource plugins never store. This stands in for a // serialization gadget (e.g. a commons-beanutils BeanComparator): the - // allowlist rejects it by class name during readObject, before the class - // is loaded or any of its logic runs. + // allowlist rejects it by class name during readObject, before the object + // is instantiated and any of its logic runs. byte[] payload = toBytes(ImmutableList.of( new File("/etc/passwd"))); assertThrows(IOException.class,