Skip to content

Commit 3c5d3e1

Browse files
Flossyclaude
andcommitted
Fix issue #58: CRITICAL TOCTOU race condition in cache checks
Fixed Time-Of-Check-Time-Of-Use race condition that could cause NullPointerException in concurrent class loading scenarios. **Problem:** Thread A: containsKey(key) → true Thread B: remove(key) → removes entry Thread A: get(key) → returns NULL → NPE! Even though ConcurrentHashMap is thread-safe, two separate operations (containsKey + get) are not atomic together. **Files Fixed:** 1. NexusClassSource.java - loadFromMaven() method 2. MavenRepositoryClassSource.java - loadClassData() method 3. MavenNexusClassSource.java - loadClassData() method **Fix:** Replaced: if (cache.containsKey(key)) { return cache.get(key); // Can return null! } With atomic single operation: byte[] data = cache.get(key); if (data != null) { return data; } This eliminates the race window between check and use. All 377 tests pass. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 6d4ed04 commit 3c5d3e1

3 files changed

Lines changed: 12 additions & 6 deletions

File tree

src/main/java/org/flossware/jclassloader/MavenNexusClassSource.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,10 @@ public MavenNexusClassSource(String nexusUrl, String repository, List<MavenArtif
7272
@Override
7373
public byte[] loadClassData(String className) throws IOException {
7474
String cacheKey = className;
75-
if (classCache.containsKey(cacheKey)) {
76-
return classCache.get(cacheKey);
75+
// Atomic get() - avoids TOCTOU race condition with contains() + get()
76+
byte[] cachedData = classCache.get(cacheKey);
77+
if (cachedData != null) {
78+
return cachedData;
7779
}
7880

7981
String classFileName = ClassNameUtil.toClassFilePath(className);

src/main/java/org/flossware/jclassloader/MavenRepositoryClassSource.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ public MavenRepositoryClassSource(String repositoryUrl, List<MavenArtifact> arti
6767
@Override
6868
public byte[] loadClassData(String className) throws IOException {
6969
String cacheKey = className;
70-
if (classCache.containsKey(cacheKey)) {
71-
return classCache.get(cacheKey);
70+
// Atomic get() - avoids TOCTOU race condition with contains() + get()
71+
byte[] cachedData = classCache.get(cacheKey);
72+
if (cachedData != null) {
73+
return cachedData;
7274
}
7375

7476
String classFileName = ClassNameUtil.toClassFilePath(className);

src/main/java/org/flossware/jclassloader/NexusClassSource.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,10 @@ private byte[] loadFromMaven(String className) throws IOException {
116116
}
117117

118118
String cachedKey = packagePath;
119-
if (jarCache.containsKey(cachedKey)) {
120-
return jarCache.get(cachedKey);
119+
// Atomic get() - avoids TOCTOU race condition with contains() + get()
120+
byte[] cachedData = jarCache.get(cachedKey);
121+
if (cachedData != null) {
122+
return cachedData;
121123
}
122124

123125
String simpleClassName = getSimpleClassName(className);

0 commit comments

Comments
 (0)