Skip to content

Commit 78b3fb0

Browse files
Add path traversal protections across multiple modules
- Implemented path traversal checks in RoboRumbleAtHome, AutoExtract, JarExtractor, and RobotFileSystemManager. - Ensured all file and directory operations validate paths against trusted directories or normalize paths. - Enhanced error reporting for invalid paths and added safeguards during directory and file creation or renaming.
1 parent 537bf55 commit 78b3fb0

File tree

7 files changed

+87
-20
lines changed

7 files changed

+87
-20
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# The version of Robocode
2-
version=1.10.1
2+
version=1.10.2
33

44
# These are set to dummy values as they don't exist when run on GitHub actions
55
ossrhUsername=dummy

robocode.host/src/main/java/net/sf/robocode/host/io/RobotFileSystemManager.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,14 @@ private void updateDataFilesFromJar() throws IOException {
295295
os = null;
296296
try {
297297
is = jarFile.getInputStream(jarEntry);
298-
os = new FileOutputStream(new File(parent, filename));
298+
File targetFile = new File(parent, filename);
299+
File canonicalParent = parent.getCanonicalFile();
300+
File canonicalTarget = targetFile.getCanonicalFile();
301+
if (!canonicalTarget.getPath().startsWith(canonicalParent.getPath())) {
302+
Logger.logError("Path traversal attempt detected: " + filename);
303+
continue;
304+
}
305+
os = new FileOutputStream(canonicalTarget);
299306
copyStream(is, os);
300307
} finally {
301308
FileUtil.cleanupStream(is);

robocode.host/src/main/java/net/sf/robocode/host/security/RobotThreadManager.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ public boolean waitForStop() {
103103

104104
Thread[] threads = new Thread[100];
105105

106-
runThreadGroup.enumerate(threads);
106+
if (runThreadGroup != null) {
107+
runThreadGroup.enumerate(threads);
108+
}
107109

108110
for (Thread thread : threads) {
109111
if (thread != null && thread != runThread && thread.isAlive()) {

robocode.installer/src/main/java/net/sf/robocode/installer/AutoExtract.java

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,10 @@ private boolean extract(File installDir) {
255255
}
256256

257257
File outputFile = new File(installDir, entryName);
258-
258+
// Path traversal protection
259+
if (!outputFile.toPath().normalize().startsWith(installDir.toPath().normalize())) {
260+
throw new RuntimeException("Bad zip entry: " + entryName);
261+
}
259262
File parentDirectory = new File(outputFile.getParent());
260263
if (!parentDirectory.exists() && !parentDirectory.mkdirs()) {
261264
System.err.println("Can't create dir: " + parentDirectory);
@@ -289,7 +292,7 @@ private boolean extract(File installDir) {
289292
if (isShFile || isCommandFile) {
290293
String filePath = outputFile.getAbsolutePath();
291294

292-
// Remove ^M (MS DOS) characters, if they exists
295+
// Remove ^M (MS DOS) characters if they exist
293296
dos2unix(filePath);
294297

295298
// Set file permissions for .sh and .command files under Unix and Mac OS X
@@ -426,25 +429,42 @@ private static boolean install(File suggestedDir) {
426429
// The .robotcache has been renamed to .data
427430
File robotsCacheDir = new File(installDir, "robots/.robotcache");
428431
File robotsDataDir = new File(installDir, "robots/.data");
429-
432+
// Path traversal protection for robotsCacheDir and robotsDataDir
433+
if (!robotsCacheDir.toPath().normalize().startsWith(installDir.toPath().normalize()) ||
434+
!robotsDataDir.toPath().normalize().startsWith(installDir.toPath().normalize())) {
435+
throw new RuntimeException("Bad robots cache/data directory");
436+
}
430437
if (robotsCacheDir.exists()) {
431438
// Rename ".robotcache" into ".data"
432-
robotsCacheDir.renameTo(robotsDataDir);
439+
if (!robotsCacheDir.renameTo(robotsDataDir)) {
440+
System.err.println("Failed to rename .robotcache to .data");
441+
}
433442
}
434-
435443
// Fix problem with .data starting with a underscore dir by
436444
// renaming files containing ".data/_" into ".data"
437445
if (robotsDataDir.exists()) {
438446
File underScoreDir = new File(robotsDataDir, "_");
447+
// Path traversal protection for underScoreDir
448+
if (!underScoreDir.toPath().normalize().startsWith(robotsDataDir.toPath().normalize())) {
449+
throw new RuntimeException("Bad underscore directory in .data");
450+
}
439451
String[] list = underScoreDir.list();
440-
441452
if (list != null) {
442453
for (String fileName : list) {
443454
File file = new File(underScoreDir, fileName);
444-
445-
file.renameTo(new File(robotsDataDir, fileName));
455+
File targetFile = new File(robotsDataDir, fileName);
456+
// Path traversal protection for file and targetFile
457+
if (!file.toPath().normalize().startsWith(underScoreDir.toPath().normalize()) ||
458+
!targetFile.toPath().normalize().startsWith(robotsDataDir.toPath().normalize())) {
459+
throw new RuntimeException("Bad file in underscore directory: " + fileName);
460+
}
461+
if (!file.renameTo(targetFile)) {
462+
System.err.println("Failed to rename " + file.getAbsolutePath() + " to " + targetFile.getAbsolutePath());
463+
}
464+
}
465+
if (!underScoreDir.delete()) {
466+
System.err.println("Failed to delete underscore directory: " + underScoreDir.getAbsolutePath());
446467
}
447-
underScoreDir.delete();
448468
}
449469
}
450470

robocode.repository/src/main/java/net/sf/robocode/repository/packager/JarExtractor.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,12 @@ public static void extractJar(URL url) {
4646
while (entry != null) {
4747
if (entry.isDirectory()) {
4848
File dir = new File(dest, entry.getName());
49-
49+
// Path traversal protection
50+
if (!dir.toPath().normalize().startsWith(dest.toPath().normalize())) {
51+
Logger.logError("Path traversal attempt detected in directory entry: " + entry.getName());
52+
entry = jarIS.getNextJarEntry();
53+
continue;
54+
}
5055
if (!dir.exists() && !dir.mkdirs()) {
5156
Logger.logError("Cannot create dir: " + dir);
5257
}
@@ -66,8 +71,12 @@ public static void extractJar(URL url) {
6671

6772
public static void extractFile(File dest, JarInputStream jarIS, JarEntry entry) throws IOException {
6873
File out = new File(dest, entry.getName());
74+
// Path traversal protection
75+
if (!out.toPath().normalize().startsWith(dest.toPath().normalize())) {
76+
Logger.logError("Path traversal attempt detected in file entry: " + entry.getName());
77+
return;
78+
}
6979
File parentDirectory = new File(out.getParent());
70-
7180
if (!parentDirectory.exists() && !parentDirectory.mkdirs()) {
7281
Logger.logError("Cannot create dir: " + parentDirectory);
7382
}

robocode.roborumble/src/main/java/roborumble/RoboRumbleAtHome.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import static net.sf.robocode.roborumble.util.PropertiesUtil.getProperties;
1818

19-
import java.util.Map;
19+
import java.nio.file.Paths;
2020
import java.util.Properties;
2121

2222

@@ -45,8 +45,12 @@ public static void main(String[] args) {
4545
System.out.println("No argument found specifying properties file. \"" + paramsFileName + "\" assumed.");
4646
}
4747

48+
// Define trusted directory
49+
String trustedDir = "./roborumble";
50+
// Validate paramsFileName is inside trustedDir
51+
String safeParamsFilePath = getSafePath(paramsFileName, trustedDir);
4852
// Read parameters for running the app
49-
Properties properties = getProperties(paramsFileName);
53+
Properties properties = getProperties(safeParamsFilePath);
5054

5155
String envUser = System.getenv("RUMBLE_USER");
5256
if (envUser != null) {
@@ -82,10 +86,12 @@ public static void main(String[] args) {
8286
boolean participantsdownloaded;
8387
String version = null;
8488
String game = paramsFileName;
85-
while (game.indexOf("/") != -1) {
86-
game = game.substring(game.indexOf("/") + 1);
89+
// Path traversal protection: extract only the filename
90+
String safeGame = Paths.get(game).getFileName().toString();
91+
if (safeGame.contains("..") || safeGame.contains("/") || safeGame.contains("\\")) {
92+
throw new RuntimeException("Invalid game filename: " + safeGame);
8793
}
88-
game = game.substring(0, game.indexOf("."));
94+
game = safeGame.substring(0, safeGame.indexOf("."));
8995

9096
do {
9197
final BattlesRunner engine = new BattlesRunner(game, properties);
@@ -125,7 +131,7 @@ public static void main(String[] args) {
125131
final boolean isMelee = melee.equals("YES");
126132

127133
boolean ready;
128-
PrepareBattles battles = new PrepareBattles(paramsFileName);
134+
PrepareBattles battles = new PrepareBattles(safeParamsFilePath);
129135

130136
if (isMelee) {
131137
System.out.println("Preparing melee battles list ...");
@@ -179,4 +185,21 @@ public static void main(String[] args) {
179185
// With Java 5 this causes a IllegalThreadStateException, but not in Java 6
180186
// System.exit(0);
181187
}
188+
189+
private static String sanitizeFileName(String fileName) {
190+
String safeName = Paths.get(fileName).getFileName().toString();
191+
if (safeName.contains("..") || safeName.contains("/") || safeName.contains("\\")) {
192+
throw new RuntimeException("Invalid file name: " + safeName);
193+
}
194+
return safeName;
195+
}
196+
197+
private static String getSafePath(String fileName, String trustedDir) {
198+
java.nio.file.Path trustedPath = Paths.get(trustedDir).toAbsolutePath().normalize();
199+
java.nio.file.Path filePath = Paths.get(fileName).toAbsolutePath().normalize();
200+
if (!filePath.startsWith(trustedPath)) {
201+
throw new RuntimeException("Path traversal or access outside trusted directory: " + fileName);
202+
}
203+
return filePath.toString();
204+
}
182205
}

versions.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## Version 1.10.2 (24-Dec-2025)
2+
3+
### Bugfix
4+
5+
- Fixing security issues (CWE-23)
6+
17
## Version 1.10.1 (09-Dec-2025)
28

39
### Bugfix

0 commit comments

Comments
 (0)