Skip to content

CRITICAL: HdfsClassSource has no size limits - OOM vulnerability #52

@sfloess

Description

@sfloess

Severity: CRITICAL

File: HdfsClassSource.java

Problem

HdfsClassSource downloads entire HDFS files into memory with NO size validation. Identical OOM vulnerability to MinioClassSource (Issue #51).

Bugs

1. Lines 37-41: Unbounded memory allocation

try (InputStream in = hdfs.open(classPath);
     ByteArrayOutputStream out = new ByteArrayOutputStream()) {

    byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }

    return out.toByteArray();
}

Attack scenario:

  1. Attacker places multi-GB file in HDFS at expected class path
  2. JClassLoader attempts to load it
  3. ByteArrayOutputStream grows to multi-GB size
  4. OutOfMemoryError - JVM dies

Impact:

  • Complete service disruption
  • Entire JVM crashes, not just this ClassLoader
  • Cannot recover without restart
  • Affects all applications in the same JVM

2. Lines 48-54: canLoad() catches IOException and returns false

Same issue as all other ClassSource implementations - cannot distinguish errors.

3. Missing timeout configuration

HDFS operations can hang indefinitely on:

  • Network partitions
  • Dead DataNodes
  • Slow networks
  • NameNode overload

No timeouts configured means threads hang FOREVER.

Required Fixes

Fix 1: Add size validation

@Override
public byte[] loadClassData(String className) throws IOException {
    Path classPath = getClassPath(className);
    
    // Check size BEFORE downloading
    FileStatus status = hdfs.getFileStatus(classPath);
    long size = status.getLen();
    
    if (size > MAX_CLASS_SIZE) {  // e.g., 10MB
        throw new IOException(
            "Class file too large: " + size + " bytes (max " + MAX_CLASS_SIZE + ")"
        );
    }
    
    if (size > Integer.MAX_VALUE) {
        throw new IOException(
            "Class file exceeds Java array limit: " + size + " bytes"
        );
    }
    
    // Safe to download
    try (InputStream in = hdfs.open(classPath)) {
        byte[] data = new byte[(int)size];
        int totalRead = 0;
        
        while (totalRead < size) {
            int n = in.read(data, totalRead, (int)size - totalRead);
            if (n == -1) break;
            totalRead += n;
        }
        
        if (totalRead != size) {
            throw new IOException(
                "Expected " + size + " bytes but read " + totalRead
            );
        }
        
        return data;
    }
}

Fix 2: Add timeout configuration

public static class Builder {
    private int socketTimeout = 30000;      // 30 seconds
    private int connectTimeout = 10000;     // 10 seconds
    
    public Builder socketTimeout(int timeoutMs) {
        if (timeoutMs < 0) {
            throw new IllegalArgumentException("Socket timeout must be >= 0");
        }
        this.socketTimeout = timeoutMs;
        return this;
    }
    
    public Builder connectTimeout(int timeoutMs) {
        if (timeoutMs < 0) {
            throw new IllegalArgumentException("Connect timeout must be >= 0");
        }
        this.connectTimeout = timeoutMs;
        return this;
    }
    
    public HdfsClassSource build() throws IOException {
        Configuration conf = configuration != null ? configuration : new Configuration();
        
        if (nameNodeUri != null) {
            conf.set("fs.defaultFS", nameNodeUri);
        }
        
        // Configure timeouts
        conf.setInt("ipc.client.connect.timeout", connectTimeout);
        conf.setInt("ipc.client.connect.max.retries", 3);
        conf.setInt("ipc.ping.interval", 10000);
        
        FileSystem hdfs = FileSystem.get(conf);
        return new HdfsClassSource(hdfs, basePath);
    }
}

Fix 3: Add input validation

private Path getClassPath(String className) {
    Objects.requireNonNull(className, "className cannot be null");
    
    if (className.isEmpty()) {
        throw new IllegalArgumentException("className cannot be empty");
    }
    
    String classFile = ClassNameUtil.toClassFilePath(className);
    String fullPath = basePath.endsWith("/") ?
        basePath + classFile :
        basePath + "/" + classFile;
    return new Path(fullPath);
}

Impact

Without these fixes:

  • OOM attacks: Trivial to crash the JVM
  • Hung threads: Network issues cause permanent thread hangs
  • No recovery: Requires kill -9 and restart

This is a CRITICAL security vulnerability - HDFS is typically used in multi-tenant environments where untrusted users can write files. A single malicious or corrupted class file can take down production systems.

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions