diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl index 0ac523fe2..ffa30114a 100644 --- a/sanitizers/sanitizers.bzl +++ b/sanitizers/sanitizers.bzl @@ -21,6 +21,7 @@ _sanitizer_class_names = [ "ClojureLangHooks", "Deserialization", "ExpressionLanguageInjection", + "FilePathTraversal", "LdapInjection", "NamingContextLookup", "OsCommandInjection", diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel index 2498c8df1..60d2e02e9 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel @@ -9,6 +9,12 @@ java_library( deps = ["//src/main/java/com/code_intelligence/jazzer/api:hooks"], ) +java_library( + name = "file_path_traversal", + srcs = ["FilePathTraversal.java"], + deps = ["//src/main/java/com/code_intelligence/jazzer/api:hooks"], +) + java_library( name = "regex_roadblocks", srcs = ["RegexRoadblocks.java"], @@ -58,6 +64,7 @@ kt_jvm_library( visibility = ["//sanitizers:__pkg__"], runtime_deps = [ ":clojure_lang_hooks", + ":file_path_traversal", ":regex_roadblocks", ":script_engine_injection", ":server_side_request_forgery", diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/FilePathTraversal.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/FilePathTraversal.java new file mode 100644 index 000000000..9ec5ea69f --- /dev/null +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/FilePathTraversal.java @@ -0,0 +1,352 @@ +package com.code_intelligence.jazzer.sanitizers; + +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical; +import com.code_intelligence.jazzer.api.HookType; +import com.code_intelligence.jazzer.api.Jazzer; +import com.code_intelligence.jazzer.api.MethodHook; + +import java.io.IOException; +import java.io.File; +import java.lang.invoke.MethodHandle; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * This tests for a file read or write of a specific file name AND + * whether that file is in an allowed directory or a descendant. + *
+ * This checks only for literal, absolute, normalized paths. It does not process symbolic links. + *
+ * This sanitizer will only trigger if {@link FilePathTraversal#ALLOWED_DIRS_KEY} is + * set as an environment variable. If that is not set, this sanitizer is a no-op. + *
+ * This does not check for reading metadata from files outside of the allowed directories.
+ */
+public class FilePathTraversal {
+ public static final String FILE_NAME_ENV_KEY = "JAZZER_FILE_SYSTEM_TRAVERSAL_FILE_NAME";
+ public static final String ALLOWED_DIRS_KEY = "jazzer.fs_allowed_dirs";
+ public static final String DEFAULT_SENTINEL = "jazzer-traversal";
+ public static final String SENTINEL =
+ (System.getenv(FILE_NAME_ENV_KEY) == null ||
+ System.getenv(FILE_NAME_ENV_KEY).trim().length() == 0) ?
+ DEFAULT_SENTINEL : System.getenv(FILE_NAME_ENV_KEY);
+
+ //intentionally skipping createLink and createSymbolicLink
+
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "createDirectory"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "createDirectories"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "createFile"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "createTempDirectory"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "createTempFile"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "delete"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "deleteIfExists"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "lines"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "newByteChannel"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "newBufferedReader"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "newBufferedWriter"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "readString"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "newBufferedReader"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "readAllBytes"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "readAllLines"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "readSymbolicLink"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "write"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "writeString"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "newInputStream"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "newOutputStream"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.probeContentType",
+ targetMethod = "open"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.channels.FileChannel",
+ targetMethod = "open"
+ )
+ public static void pathFirstArgHook(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ if (arguments.length > 0) {
+ Object argObj = arguments[0];
+ if (argObj instanceof Path) {
+ checkPath((Path)argObj);
+ }
+ }
+ }
+
+ /**
+ * Checks to confirm that a path that is read from or written to
+ * is in an allowed directory.
+ *
+ * @param method
+ * @param thisObject
+ * @param arguments
+ * @param hookId
+ */
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "copy"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "mismatch"
+ )
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.nio.file.Files",
+ targetMethod = "move"
+ )
+ public static void copyMismatchMvHook(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ if (arguments.length > 1) {
+ Object from = arguments[0];
+ if (from instanceof Path) {
+ checkPath((Path) from);
+ }
+ Object to = arguments[1];
+ if (to instanceof Path) {
+ checkPath((Path) to);
+ }
+ }
+ }
+
+
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.io.FileReader",
+ targetMethod = "