Skip to content

Commit a536b63

Browse files
committed
feat: add user-defined allow-predicate to file path traversal sanitizer
1 parent 2a738f8 commit a536b63

File tree

2 files changed

+71
-16
lines changed

2 files changed

+71
-16
lines changed

sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/FilePathTraversal.java

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Optional;
2828
import java.util.concurrent.ThreadLocalRandom;
2929
import java.util.concurrent.atomic.AtomicReference;
30+
import java.util.function.Predicate;
3031
import java.util.function.Supplier;
3132

3233
/**
@@ -47,6 +48,8 @@ public class FilePathTraversal {
4748
// Set via reflection by Jazzer's BugDetectors API.
4849
public static final AtomicReference<Supplier<Path>> target =
4950
new AtomicReference<>(() -> DEFAULT_TARGET);
51+
public static final AtomicReference<Predicate<Path>> checkPath =
52+
new AtomicReference<>((Path ignored) -> true);
5053

5154
// When guiding the fuzzer towards the target path, sometimes both the absolute and relative paths
5255
// are valid. In this case, we toggle between them randomly.
@@ -271,20 +274,8 @@ private static void detectAndGuidePathTraversal(Object obj, int hookId) {
271274
if (obj == null) {
272275
return;
273276
}
274-
Path targetPath = target.get().get();
275-
276-
// Users can set the atomic function to return null to disable the sanitizer.
277-
if (targetPath == null) {
278-
return;
279-
}
280-
targetPath = targetPath.normalize();
281277

282-
Path currentDir = Paths.get("").toAbsolutePath();
283-
Path absTarget = toAbsolutePath(targetPath, currentDir).orElse(null);
284-
Path relTarget = toRelativePath(targetPath, currentDir).orElse(null);
285-
if (absTarget == null && relTarget == null) {
286-
return;
287-
}
278+
Path targetPath = target.get().get();
288279

289280
String query;
290281
if (obj instanceof Path) {
@@ -305,11 +296,41 @@ private static void detectAndGuidePathTraversal(Object obj, int hookId) {
305296
return;
306297
}
307298

299+
Predicate<Path> checkAllowed = checkPath.get();
300+
boolean isPathAllowed = checkAllowed == null || checkAllowed.test(Paths.get(query).normalize());
301+
if (!isPathAllowed) {
302+
Jazzer.reportFindingFromHook(
303+
new FuzzerSecurityIssueCritical(
304+
"File path traversal: "
305+
+ query
306+
+ "\n Path is not allowed by the user-defined predicate."
307+
+ "\n Current path traversal fuzzing target: "
308+
+ targetPath));
309+
}
310+
311+
// Users can set the atomic function to return null to disable the fuzzer guidance.
312+
if (targetPath == null) {
313+
return;
314+
}
315+
targetPath = targetPath.normalize();
316+
317+
Path currentDir = Paths.get("").toAbsolutePath();
318+
Path absTarget = toAbsolutePath(targetPath, currentDir).orElse(null);
319+
Path relTarget = toRelativePath(targetPath, currentDir).orElse(null);
320+
if (absTarget == null && relTarget == null) {
321+
return;
322+
}
323+
308324
if ((absTarget != null && absTarget.toString().equals(query))
309325
|| (relTarget != null && relTarget.toString().equals(query))) {
310326
Jazzer.reportFindingFromHook(
311-
new FuzzerSecurityIssueCritical("File path traversal: " + query));
327+
new FuzzerSecurityIssueCritical(
328+
"File path traversal: "
329+
+ query
330+
+ "\n Reached current path traversal fuzzing target: "
331+
+ targetPath));
312332
}
333+
313334
if (absTarget != null && relTarget != null) {
314335
if (guideTowardsAbsoluteTargetPath) {
315336
Jazzer.guideTowardsContainment(query, relTarget.toString(), hookId);

src/main/java/com/code_intelligence/jazzer/api/BugDetectors.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.nio.file.Path;
2020
import java.util.concurrent.atomic.AtomicReference;
2121
import java.util.function.BiPredicate;
22+
import java.util.function.Predicate;
2223
import java.util.function.Supplier;
2324

2425
/** Provides static functions that configure the behavior of bug detectors provided by Jazzer. */
@@ -88,11 +89,13 @@ public static SilentCloseable allowNetworkConnections(
8889
getSanitizerVariable("com.code_intelligence.jazzer.sanitizers.FilePathTraversal", "target");
8990

9091
/**
91-
* Sets the target for file path traversal sanitization.
92+
* Sets the target for file path traversal sanitization. If the target is reached, a finding is
93+
* thrown. The target is also used to guide the fuzzer to intentionally trigger file path
94+
* traversal.
9295
*
9396
* <p>By default, the file path traversal target is set to {@code "../jazzer-traversal"}.
9497
*
95-
* <p>Setting the target to {@code () -> null } will disable file path traversal sanitization.
98+
* <p>Setting the target path to {@code null } will disable the guidance.
9699
*
97100
* <p>By wrapping the call into a try-with-resources statement, the target can be configured to
98101
* apply to individual parts of the fuzz test only:
@@ -111,6 +114,37 @@ public static SilentCloseable setFilePathTraversalTarget(Supplier<Path> pathTrav
111114
return setSanitizerVariable(pathTraversalTarget, currentPathTraversalTarget);
112115
}
113116

117+
private static final AtomicReference<Predicate<Path>> currentCheckPath =
118+
getSanitizerVariable(
119+
"com.code_intelligence.jazzer.sanitizers.FilePathTraversal", "checkPath");
120+
121+
/**
122+
* Sets the predicate that determines if a file path is allowed to be accessed. Paths that
123+
* are not allowed will trigger a file path traversal finding. If you use this method, don't
124+
* forget to set the fuzzing target with {@code setFilePathTraversalTarget} that aligns with this
125+
* predicate, because both {@code target} and {@code checkPath} can trigger a finding
126+
* independently.
127+
*
128+
* <p>By default, all file paths are allowed. Setting the predicate to {@code false} will trigger
129+
* a file path traversal finding for any file path access.
130+
*
131+
* <p>By wrapping the call into a try-with-resources statement, the predicate can be configured to
132+
* apply to individual parts of the fuzz test only:
133+
*
134+
* <pre>{@code
135+
* try (SilentCloseable unused = BugDetectors.setFilePathTraversalAllowPath(
136+
* (Path p) -> p.toString().contains("secret"))) {
137+
* // Perform operations that require file path traversal sanitization
138+
* }
139+
* }</pre>
140+
*
141+
* @param checkPath a predicate that evaluates to {@code true} if the file path is allowed
142+
* @return a {@link SilentCloseable} that restores the previously set predicate when closed
143+
*/
144+
public static SilentCloseable setFilePathTraversalAllowPath(Predicate<Path> checkPath) {
145+
return setSanitizerVariable(checkPath, currentCheckPath);
146+
}
147+
114148
private static <T> AtomicReference<T> getSanitizerVariable(
115149
String sanitizerClassName, String fieldName) {
116150
try {

0 commit comments

Comments
 (0)