Skip to content

Commit 584136e

Browse files
Flossyclaude
andcommitted
fix: add size validation to LocalClassSource and remove redundant check
- Add MAX_CLASS_SIZE constant (10MB default) to prevent OOM on large files - Add configurable maxClassSize parameter to constructor - Check file size before reading with Files.readAllBytes() - Remove redundant path traversal check on lines 63-65 (checked className before conversion, making it ineffective) - The real protection is the startsWith() check after normalization - Improve error messages with separate checks for "not found" vs "not a file" Fixes #55 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 6382d35 commit 584136e

1 file changed

Lines changed: 46 additions & 12 deletions

File tree

src/main/java/org/flossware/classloader/LocalClassSource.java

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,38 @@
1313
* This is the fastest class loading source as it reads directly from disk.
1414
*/
1515
public class LocalClassSource implements ClassSource {
16+
private static final long MAX_CLASS_SIZE = 10 * 1024 * 1024; // 10MB default max size
1617
private final Path basePath;
18+
private final long maxClassSize;
1719

1820
/**
19-
* Creates a local class source with the specified base path.
21+
* Creates a local class source with the specified base path and max class size.
2022
*
2123
* @param basePath The base directory path containing class files
24+
* @param maxClassSize Maximum size of a class file in bytes
2225
* @throws NullPointerException if basePath is null
26+
* @throws IllegalArgumentException if maxClassSize is not positive
2327
*/
24-
public LocalClassSource(Path basePath) {
28+
public LocalClassSource(Path basePath, long maxClassSize) {
2529
this.basePath = Objects.requireNonNull(basePath, "basePath cannot be null").toAbsolutePath().normalize();
30+
if (maxClassSize <= 0) {
31+
throw new IllegalArgumentException("maxClassSize must be positive");
32+
}
33+
this.maxClassSize = maxClassSize;
34+
}
35+
36+
/**
37+
* Creates a local class source with the specified base path and default max size (10MB).
38+
*
39+
* @param basePath The base directory path containing class files
40+
* @throws NullPointerException if basePath is null
41+
*/
42+
public LocalClassSource(Path basePath) {
43+
this(basePath, MAX_CLASS_SIZE);
2644
}
2745

2846
/**
29-
* Creates a local class source with the specified base path string.
47+
* Creates a local class source with the specified base path string and default max size (10MB).
3048
*
3149
* @param basePath The base directory path string containing class files
3250
*/
@@ -37,10 +55,30 @@ public LocalClassSource(String basePath) {
3755
@Override
3856
public byte[] loadClassData(String className) throws IOException {
3957
Path classFile = getClassFilePath(className);
40-
if (Files.exists(classFile) && Files.isRegularFile(classFile)) {
41-
return Files.readAllBytes(classFile);
58+
59+
if (!Files.exists(classFile)) {
60+
throw new IOException("Class file not found: " + classFile);
61+
}
62+
63+
if (!Files.isRegularFile(classFile)) {
64+
throw new IOException("Not a regular file: " + classFile);
65+
}
66+
67+
// Check size before reading to prevent OOM on large files
68+
long size = Files.size(classFile);
69+
if (size > maxClassSize) {
70+
throw new IOException(
71+
"Class file too large: " + size + " bytes (max " + maxClassSize + ")"
72+
);
4273
}
43-
throw new IOException("Class file not found: " + classFile);
74+
75+
if (size > Integer.MAX_VALUE) {
76+
throw new IOException(
77+
"Class file exceeds Java array limit: " + size + " bytes"
78+
);
79+
}
80+
81+
return Files.readAllBytes(classFile);
4482
}
4583

4684
@Override
@@ -59,15 +97,11 @@ public String getDescription() {
5997
}
6098

6199
private Path getClassFilePath(String className) throws IOException {
62-
// Prevent path traversal attacks
63-
if (className.contains("..") || className.contains("/") || className.contains("\\")) {
64-
throw new IOException("Invalid class name (potential path traversal): " + className);
65-
}
66-
100+
// Convert class name to file path
67101
String fileName = ClassNameUtil.toClassFilePath(className);
68102
Path resolvedPath = basePath.resolve(fileName).normalize();
69103

70-
// Ensure the resolved path is within basePath
104+
// Ensure the resolved path is within basePath (this is the real protection)
71105
if (!resolvedPath.startsWith(basePath)) {
72106
throw new IOException("Path traversal attempt detected: " + className);
73107
}

0 commit comments

Comments
 (0)