Skip to content

CRITICAL: MavenRepositoryClassSource silently swallows errors + missing timeouts + OOM risk #60

@sfloess

Description

@sfloess

Severity: CRITICAL

File: MavenRepositoryClassSource.java

Problem

Same bugs as MavenNexusClassSource (Issue #59): silent exception swallowing, no timeouts, no size limits. Also has the same TOCTOU bug that was already reported in Issue #58.

Bug Details

Lines 76-85: Silent exception swallowing

for (MavenArtifact artifact : artifacts) {
    try {
        String jarUrl = buildJarUrl(artifact);
        byte[] classData = extractClassFromJar(jarUrl, classFileName);
        classCache.put(cacheKey, classData);
        return classData;
    } catch (IOException e) {
        // Continue to next artifact if class not found in this one
    }
}

throw new IOException("Class not found in any configured Maven artifacts: " + className);

Problem: Identical to Issue #59 - all errors are silently discarded

When loading fails, you get:

Class not found in any configured Maven artifacts: com.example.MyClass

No indication whether:

  • Maven Central is down
  • Wrong artifact coordinates
  • Network timeout
  • Auth failure
  • Corrupted JAR

Lines 110-139: extractClassFromJar() critical bugs

1. Line 112: No timeout

HttpURLConnection connection = (HttpURLConnection) url.openConnection();

Can hang forever - same as Issue #53

2. Lines 127-133: No size validation

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

Can OOM - same as Issues #51-54

3. Line 122: Downloads entire JAR streamed to memory
No Content-Length check before streaming JAR

Lines 70-72: TOCTOU race condition

String cacheKey = className;
if (classCache.containsKey(cacheKey)) {
    return classCache.get(cacheKey);  // ← Can return null!
}

Already reported in Issue #58 - uses containsKey() + get() pattern which has race condition.

Note: MavenNexusClassSource FIXED this bug (line 76 uses atomic get()), but MavenRepositoryClassSource still has it.

Required Fixes

Fix 1: Use atomic get() instead of containsKey()

@Override
public byte[] loadClassData(String className) throws IOException {
    // ATOMIC - fixes TOCTOU race from Issue #58
    byte[] cachedData = classCache.get(className);
    if (cachedData != null) {
        return cachedData;
    }

    String classFileName = ClassNameUtil.toClassFilePath(className);
    List<String> errorMessages = new ArrayList<>();

    for (MavenArtifact artifact : artifacts) {
        try {
            String jarUrl = buildJarUrl(artifact);
            byte[] classData = extractClassFromJar(jarUrl, classFileName);
            classCache.put(className, classData);
            return classData;
        } catch (IOException e) {
            // Accumulate errors instead of silently swallowing
            String errorMsg = String.format("Artifact %s - %s",
                artifact.toString(), e.getMessage());
            errorMessages.add(errorMsg);
            
            if (logger.isDebugEnabled()) {
                logger.debug("Failed to load {} from {}", className, artifact, e);
            }
        }
    }

    // Detailed error message
    String allErrors = String.join("\n  - ", errorMessages);
    throw new IOException(
        "Class not found in any of " + artifacts.size() + " configured Maven artifacts: " +
        className + "\nAttempted artifacts:\n  - " + allErrors
    );
}

Fix 2: Add timeouts and size validation

private byte[] extractClassFromJar(String jarUrl, String classFileName) throws IOException {
    URL url = new URL(jarUrl);
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setConnectTimeout(10000);  // 10 seconds
    connection.setReadTimeout(30000);     // 30 seconds
    AuthHelper.configureAuth(connection, authConfig);
    connection.setRequestMethod("GET");

    int responseCode = connection.getResponseCode();
    if (responseCode != HttpURLConnection.HTTP_OK) {
        throw new IOException("HTTP " + responseCode + " for JAR: " + jarUrl);
    }

    // Check JAR size before downloading
    long contentLength = connection.getContentLengthLong();
    if (contentLength > MAX_JAR_SIZE) {
        throw new IOException(
            "JAR too large: " + contentLength + " bytes (max " + MAX_JAR_SIZE + ")"
        );
    }

    try (InputStream in = connection.getInputStream();
         JarInputStream jarIn = new JarInputStream(in)) {

        JarEntry entry;
        while ((entry = jarIn.getNextJarEntry()) != null) {
            if (entry.getName().equals(classFileName) && !entry.isDirectory()) {
                long size = entry.getSize();
                
                if (size > MAX_CLASS_SIZE) {
                    throw new IOException(
                        "Class too large: " + size + " bytes (max " + MAX_CLASS_SIZE + ")"
                    );
                }
                
                // Read with size enforcement
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
                long totalRead = 0;
                int bytesRead;
                
                while ((bytesRead = jarIn.read(buffer)) != -1) {
                    totalRead += bytesRead;
                    if (totalRead > MAX_CLASS_SIZE) {
                        throw new IOException("Class exceeded size limit: " + totalRead);
                    }
                    out.write(buffer, 0, bytesRead);
                }
                
                return out.toByteArray();
            }
        }
    }

    throw new IOException("Class not found in JAR: " + classFileName + " (URL: " + jarUrl + ")");
}

Impact

Current bugs:

  1. Silent exception swallowing - impossible to debug
  2. TOCTOU race condition - random NPEs (duplicate of Issue CRITICAL: TOCTOU race condition in cache checks causes NullPointerException #58)
  3. No timeouts - threads hang forever
  4. No size limits - OOM crashes

With fixes:

  • Detailed error messages showing all failure reasons
  • Thread-safe cache access
  • Bounded resource usage
  • Production-ready reliability

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