From b63c7e2050c2628da5cddd29fb639e785e0fc7dd Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Tue, 14 Apr 2026 15:27:43 +0200 Subject: [PATCH 1/6] feat(rasp): instrument FileOutputStream/FileInputStream(File) constructors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add RASP callsite coverage for File-argument constructors that were previously not instrumented: - FileOutputStream(File) and FileOutputStream(File, boolean): call FileIORaspHelper.INSTANCE.beforeFileWritten(file.getPath()) - FileInputStream(File): call FileIORaspHelper.INSTANCE.beforeFileLoaded(file.getPath()) No IAST changes — the File-based constructors delegate path resolution to the JVM, so IAST taint tracking via the String constructor already covers those code paths at a higher level. Tests added following the existing RASP test pattern. --- .../java/lang/FileInputStreamCallSite.java | 8 ++++++ .../java/lang/FileOutputStreamCallSite.java | 9 +++++++ .../io/FileInputStreamCallSiteTest.groovy | 13 ++++++++++ .../io/FileOutputStreamCallSiteTest.groovy | 26 +++++++++++++++++++ .../foo/bar/TestFileInputStreamSuite.java | 5 ++++ .../foo/bar/TestFileOutputStreamSuite.java | 10 +++++++ 6 files changed, 71 insertions(+) diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileInputStreamCallSite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileInputStreamCallSite.java index 4c293efb859..9772ebadddb 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileInputStreamCallSite.java +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileInputStreamCallSite.java @@ -7,6 +7,7 @@ import datadog.trace.api.iast.Sink; import datadog.trace.api.iast.VulnerabilityTypes; import datadog.trace.api.iast.sink.PathTraversalModule; +import java.io.File; import javax.annotation.Nullable; @Sink(VulnerabilityTypes.PATH_TRAVERSAL) @@ -23,6 +24,13 @@ public static void beforeConstructor(@CallSite.Argument @Nullable final String p } } + @CallSite.Before("void java.io.FileInputStream.(java.io.File)") + public static void beforeConstructorFile(@CallSite.Argument @Nullable final File file) { + if (file != null) { + raspCallback(file.getPath()); + } + } + private static void iastCallback(String path) { final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL; if (module != null) { diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileOutputStreamCallSite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileOutputStreamCallSite.java index 478a4ec795e..5f6dc215be3 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileOutputStreamCallSite.java +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileOutputStreamCallSite.java @@ -7,6 +7,7 @@ import datadog.trace.api.iast.Sink; import datadog.trace.api.iast.VulnerabilityTypes; import datadog.trace.api.iast.sink.PathTraversalModule; +import java.io.File; import javax.annotation.Nullable; @Sink(VulnerabilityTypes.PATH_TRAVERSAL) @@ -24,6 +25,14 @@ public static void beforeConstructor(@CallSite.Argument(0) @Nullable final Strin } } + @CallSite.Before("void java.io.FileOutputStream.(java.io.File)") + @CallSite.Before("void java.io.FileOutputStream.(java.io.File, boolean)") + public static void beforeConstructorFile(@CallSite.Argument(0) @Nullable final File file) { + if (file != null) { + raspCallback(file.getPath()); + } + } + private static void iastCallback(String path) { final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL; if (module != null) { diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileInputStreamCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileInputStreamCallSiteTest.groovy index 5aca165af9f..05e808e8a58 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileInputStreamCallSiteTest.groovy +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileInputStreamCallSiteTest.groovy @@ -32,4 +32,17 @@ class FileInputStreamCallSiteTest extends BaseIoRaspCallSiteTest { then: 1 * helper.beforeFileLoaded(path) } + + void 'test RASP new file input stream with file'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_file.txt') + + when: + TestFileInputStreamSuite.newFileInputStream(file) + + then: + 1 * helper.beforeFileLoaded(file.path) + } } diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileOutputStreamCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileOutputStreamCallSiteTest.groovy index e59e48c9193..1c471b98eb8 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileOutputStreamCallSiteTest.groovy +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileOutputStreamCallSiteTest.groovy @@ -60,4 +60,30 @@ class FileOutputStreamCallSiteTest extends BaseIoRaspCallSiteTest { then: 1 * helper.beforeFileWritten(path) } + + void 'test RASP new file output stream with file'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_file_1.txt') + + when: + TestFileOutputStreamSuite.newFileOutputStream(file) + + then: + 1 * helper.beforeFileWritten(file.path) + } + + void 'test RASP new file output stream with file and append'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_file_2.txt') + + when: + TestFileOutputStreamSuite.newFileOutputStream(file, false) + + then: + 1 * helper.beforeFileWritten(file.path) + } } diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileInputStreamSuite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileInputStreamSuite.java index 45b438fa6b4..ab34ff21ea3 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileInputStreamSuite.java +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileInputStreamSuite.java @@ -1,5 +1,6 @@ package foo.bar; +import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -8,4 +9,8 @@ public class TestFileInputStreamSuite { public static FileInputStream newFileInputStream(final String path) throws FileNotFoundException { return new FileInputStream(path); } + + public static FileInputStream newFileInputStream(final File file) throws FileNotFoundException { + return new FileInputStream(file); + } } diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileOutputStreamSuite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileOutputStreamSuite.java index 5d8c2110697..b294db30e76 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileOutputStreamSuite.java +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileOutputStreamSuite.java @@ -1,5 +1,6 @@ package foo.bar; +import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -14,4 +15,13 @@ public static FileOutputStream newFileOutputStream(final String path, final bool throws FileNotFoundException { return new FileOutputStream(path, append); } + + public static FileOutputStream newFileOutputStream(final File file) throws FileNotFoundException { + return new FileOutputStream(file); + } + + public static FileOutputStream newFileOutputStream(final File file, final boolean append) + throws FileNotFoundException { + return new FileOutputStream(file, append); + } } From b1e991e3716634d3074465730c44710906381621 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Tue, 14 Apr 2026 16:36:01 +0200 Subject: [PATCH 2/6] feat(appsec): extend RASP file I/O coverage to FileReader, FileWriter, RandomAccessFile, Files.* and FileChannel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends RASP callsite instrumentation (APPSEC-61874) beyond FileInputStream/FileOutputStream to all remaining Java file I/O APIs that were not covered. No IAST changes. New callsites: - FileReaderCallSite: FileReader(String/File) + Java 11+ Charset variants → beforeFileLoaded - FileWriterCallSite: FileWriter(String/File/boolean) + Java 11+ Charset variants → beforeFileWritten - RandomAccessFileCallSite: RandomAccessFile(String/File, mode) → beforeFileLoaded for "r", both beforeFileLoaded + beforeFileWritten for "rw"/"rws"/"rwd" - FilesCallSite: all Files.* read and write methods (newOutputStream, copy(IS,Path), write, writeString, newBufferedWriter, move, newInputStream, readAllBytes, readAllLines, readString, newBufferedReader, lines) - FileChannelCallSite: FileChannel.open(Path, ...) → fires both read and write callbacks Extended callsites: - PathCallSite: add resolve(Path) and resolveSibling(Path) → beforeFileLoaded - PathsCallSite: add Path.of(String[], URI) (Java 11+) → beforeFileLoaded FileIORaspHelper: add beforeRandomAccessFileOpened(path, mode) helper Relates to #11084 and #11113 --- .../java/lang/FileChannelCallSite.java | 25 ++ .../java/lang/FileIORaspHelper.java | 12 + .../java/lang/FileReaderCallSite.java | 34 +++ .../java/lang/FileWriterCallSite.java | 40 ++++ .../java/lang/FilesCallSite.java | 78 ++++++ .../java/lang/PathCallSite.java | 9 + .../java/lang/PathsCallSite.java | 17 ++ .../java/lang/RandomAccessFileCallSite.java | 30 +++ .../java/io/FileChannelCallSiteTest.groovy | 52 ++++ .../java/io/FileReaderCallSiteTest.groovy | 33 +++ .../java/io/FileWriterCallSiteTest.groovy | 61 +++++ .../java/io/FilesCallSiteTest.groovy | 225 ++++++++++++++++++ .../java/io/PathCallSiteTest.groovy | 28 +++ .../java/io/PathsCallSiteTest.groovy | 1 + .../io/RandomAccessFileCallSiteTest.groovy | 59 +++++ .../java/foo/bar/TestFileChannelSuite.java | 35 +++ .../java/foo/bar/TestFileReaderSuite.java | 16 ++ .../java/foo/bar/TestFileWriterSuite.java | 25 ++ .../src/test/java/foo/bar/TestFilesSuite.java | 100 ++++++++ .../src/test/java/foo/bar/TestPathSuite.java | 8 + .../foo/bar/TestRandomAccessFileSuite.java | 18 ++ 21 files changed, 906 insertions(+) create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileChannelCallSite.java create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileReaderCallSite.java create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileWriterCallSite.java create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FilesCallSite.java create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/RandomAccessFileCallSite.java create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileChannelCallSiteTest.groovy create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileReaderCallSiteTest.groovy create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileWriterCallSiteTest.groovy create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FilesCallSiteTest.groovy create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/RandomAccessFileCallSiteTest.groovy create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileChannelSuite.java create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileReaderSuite.java create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileWriterSuite.java create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFilesSuite.java create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestRandomAccessFileSuite.java diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileChannelCallSite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileChannelCallSite.java new file mode 100644 index 00000000000..3538885208d --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileChannelCallSite.java @@ -0,0 +1,25 @@ +package datadog.trace.instrumentation.java.lang; + +import datadog.trace.agent.tooling.csi.CallSite; +import datadog.trace.api.appsec.RaspCallSites; +import java.nio.file.Path; +import javax.annotation.Nullable; + +@CallSite( + spi = {RaspCallSites.class}, + helpers = FileIORaspHelper.class) +public class FileChannelCallSite { + + @CallSite.Before( + "java.nio.channels.FileChannel java.nio.channels.FileChannel.open(java.nio.file.Path, java.nio.file.OpenOption[])") + @CallSite.Before( + "java.nio.channels.FileChannel java.nio.channels.FileChannel.open(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])") + public static void beforeOpen(@CallSite.Argument(0) @Nullable final Path path) { + if (path != null) { + // Fire both read and write callbacks: the WAF determines whether to block + // based on the full request context (e.g. zipslip requires body.filenames too). + FileIORaspHelper.INSTANCE.beforeFileLoaded(path.toString()); + FileIORaspHelper.INSTANCE.beforeFileWritten(path.toString()); + } + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileIORaspHelper.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileIORaspHelper.java index c3b48919977..7dd48871dfe 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileIORaspHelper.java +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileIORaspHelper.java @@ -101,6 +101,18 @@ public void beforeFileWritten(@Nonnull final String path) { invokeRaspCallback(EVENTS.fileWritten(), path); } + /** + * Fires {@link #beforeFileLoaded} for read modes ("r"), and both {@link #beforeFileLoaded} and + * {@link #beforeFileWritten} for write modes ("rw", "rws", "rwd"). + */ + public void beforeRandomAccessFileOpened(@Nonnull final String path, @Nonnull final String mode) { + // "r" = read only; "rw", "rws", "rwd" = read + write + beforeFileLoaded(path); + if (mode.length() > 1) { + beforeFileWritten(path); + } + } + private void invokeRaspCallback( EventType>> eventType, @Nonnull final String path) { diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileReaderCallSite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileReaderCallSite.java new file mode 100644 index 00000000000..808a2b8dfcd --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileReaderCallSite.java @@ -0,0 +1,34 @@ +package datadog.trace.instrumentation.java.lang; + +import datadog.trace.agent.tooling.csi.CallSite; +import datadog.trace.api.appsec.RaspCallSites; +import java.io.File; +import javax.annotation.Nullable; + +@CallSite( + spi = {RaspCallSites.class}, + helpers = FileIORaspHelper.class) +public class FileReaderCallSite { + + @CallSite.Before("void java.io.FileReader.(java.lang.String)") + // Java 11+: FileReader(String, Charset) + @CallSite.Before("void java.io.FileReader.(java.lang.String, java.nio.charset.Charset)") + public static void beforeConstructor(@CallSite.Argument(0) @Nullable final String path) { + if (path != null) { + raspCallback(path); + } + } + + @CallSite.Before("void java.io.FileReader.(java.io.File)") + // Java 11+: FileReader(File, Charset) + @CallSite.Before("void java.io.FileReader.(java.io.File, java.nio.charset.Charset)") + public static void beforeConstructorFile(@CallSite.Argument(0) @Nullable final File file) { + if (file != null) { + raspCallback(file.getPath()); + } + } + + private static void raspCallback(final String path) { + FileIORaspHelper.INSTANCE.beforeFileLoaded(path); + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileWriterCallSite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileWriterCallSite.java new file mode 100644 index 00000000000..c6a4bb98acb --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileWriterCallSite.java @@ -0,0 +1,40 @@ +package datadog.trace.instrumentation.java.lang; + +import datadog.trace.agent.tooling.csi.CallSite; +import datadog.trace.api.appsec.RaspCallSites; +import java.io.File; +import javax.annotation.Nullable; + +@CallSite( + spi = {RaspCallSites.class}, + helpers = FileIORaspHelper.class) +public class FileWriterCallSite { + + @CallSite.Before("void java.io.FileWriter.(java.lang.String)") + @CallSite.Before("void java.io.FileWriter.(java.lang.String, boolean)") + // Java 11+: FileWriter(String, Charset) and FileWriter(String, Charset, boolean) + @CallSite.Before("void java.io.FileWriter.(java.lang.String, java.nio.charset.Charset)") + @CallSite.Before( + "void java.io.FileWriter.(java.lang.String, java.nio.charset.Charset, boolean)") + public static void beforeConstructor(@CallSite.Argument(0) @Nullable final String path) { + if (path != null) { + raspCallback(path); + } + } + + @CallSite.Before("void java.io.FileWriter.(java.io.File)") + @CallSite.Before("void java.io.FileWriter.(java.io.File, boolean)") + // Java 11+: FileWriter(File, Charset) and FileWriter(File, Charset, boolean) + @CallSite.Before("void java.io.FileWriter.(java.io.File, java.nio.charset.Charset)") + @CallSite.Before( + "void java.io.FileWriter.(java.io.File, java.nio.charset.Charset, boolean)") + public static void beforeConstructorFile(@CallSite.Argument(0) @Nullable final File file) { + if (file != null) { + raspCallback(file.getPath()); + } + } + + private static void raspCallback(final String path) { + FileIORaspHelper.INSTANCE.beforeFileWritten(path); + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FilesCallSite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FilesCallSite.java new file mode 100644 index 00000000000..c328974412e --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FilesCallSite.java @@ -0,0 +1,78 @@ +package datadog.trace.instrumentation.java.lang; + +import datadog.trace.agent.tooling.csi.CallSite; +import datadog.trace.api.appsec.RaspCallSites; +import java.nio.file.Path; +import javax.annotation.Nullable; + +@CallSite( + spi = {RaspCallSites.class}, + helpers = FileIORaspHelper.class) +public class FilesCallSite { + + // ===================== WRITE ===================== + + @CallSite.Before( + "java.io.OutputStream java.nio.file.Files.newOutputStream(java.nio.file.Path, java.nio.file.OpenOption[])") + @CallSite.Before( + "java.nio.file.Path java.nio.file.Files.write(java.nio.file.Path, byte[], java.nio.file.OpenOption[])") + @CallSite.Before( + "java.nio.file.Path java.nio.file.Files.write(java.nio.file.Path, java.lang.Iterable, java.nio.charset.Charset, java.nio.file.OpenOption[])") + @CallSite.Before( + "java.nio.file.Path java.nio.file.Files.write(java.nio.file.Path, java.lang.Iterable, java.nio.file.OpenOption[])") + // Java 11+: Files.writeString variants + @CallSite.Before( + "java.nio.file.Path java.nio.file.Files.writeString(java.nio.file.Path, java.lang.CharSequence, java.nio.file.OpenOption[])") + @CallSite.Before( + "java.nio.file.Path java.nio.file.Files.writeString(java.nio.file.Path, java.lang.CharSequence, java.nio.charset.Charset, java.nio.file.OpenOption[])") + @CallSite.Before( + "java.io.BufferedWriter java.nio.file.Files.newBufferedWriter(java.nio.file.Path, java.nio.charset.Charset, java.nio.file.OpenOption[])") + @CallSite.Before( + "java.io.BufferedWriter java.nio.file.Files.newBufferedWriter(java.nio.file.Path, java.nio.file.OpenOption[])") + public static void beforeWrite(@CallSite.Argument(0) @Nullable final Path path) { + if (path != null) { + FileIORaspHelper.INSTANCE.beforeFileWritten(path.toString()); + } + } + + @CallSite.Before( + "long java.nio.file.Files.copy(java.io.InputStream, java.nio.file.Path, java.nio.file.CopyOption[])") + public static void beforeCopyFromStream(@CallSite.Argument(1) @Nullable final Path target) { + if (target != null) { + FileIORaspHelper.INSTANCE.beforeFileWritten(target.toString()); + } + } + + @CallSite.Before( + "java.nio.file.Path java.nio.file.Files.move(java.nio.file.Path, java.nio.file.Path, java.nio.file.CopyOption[])") + public static void beforeMove(@CallSite.Argument(1) @Nullable final Path target) { + if (target != null) { + FileIORaspHelper.INSTANCE.beforeFileWritten(target.toString()); + } + } + + // ===================== READ ===================== + + @CallSite.Before( + "java.io.InputStream java.nio.file.Files.newInputStream(java.nio.file.Path, java.nio.file.OpenOption[])") + @CallSite.Before("byte[] java.nio.file.Files.readAllBytes(java.nio.file.Path)") + @CallSite.Before( + "java.util.List java.nio.file.Files.readAllLines(java.nio.file.Path, java.nio.charset.Charset)") + @CallSite.Before("java.util.List java.nio.file.Files.readAllLines(java.nio.file.Path)") + // Java 11+: Files.readString variants + @CallSite.Before("java.lang.String java.nio.file.Files.readString(java.nio.file.Path)") + @CallSite.Before( + "java.lang.String java.nio.file.Files.readString(java.nio.file.Path, java.nio.charset.Charset)") + @CallSite.Before( + "java.io.BufferedReader java.nio.file.Files.newBufferedReader(java.nio.file.Path, java.nio.charset.Charset)") + @CallSite.Before( + "java.io.BufferedReader java.nio.file.Files.newBufferedReader(java.nio.file.Path)") + @CallSite.Before( + "java.util.stream.Stream java.nio.file.Files.lines(java.nio.file.Path, java.nio.charset.Charset)") + @CallSite.Before("java.util.stream.Stream java.nio.file.Files.lines(java.nio.file.Path)") + public static void beforeRead(@CallSite.Argument(0) @Nullable final Path path) { + if (path != null) { + FileIORaspHelper.INSTANCE.beforeFileLoaded(path.toString()); + } + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/PathCallSite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/PathCallSite.java index 19015bed8b0..38a80b60141 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/PathCallSite.java +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/PathCallSite.java @@ -7,6 +7,7 @@ import datadog.trace.api.iast.Sink; import datadog.trace.api.iast.VulnerabilityTypes; import datadog.trace.api.iast.sink.PathTraversalModule; +import java.nio.file.Path; import javax.annotation.Nullable; @Sink(VulnerabilityTypes.PATH_TRAVERSAL) @@ -24,6 +25,14 @@ public static void beforeResolve(@CallSite.Argument @Nullable final String other } } + @CallSite.Before("java.nio.file.Path java.nio.file.Path.resolve(java.nio.file.Path)") + @CallSite.Before("java.nio.file.Path java.nio.file.Path.resolveSibling(java.nio.file.Path)") + public static void beforeResolveWithPath(@CallSite.Argument @Nullable final Path other) { + if (other != null) { + raspCallback(other.toString()); + } + } + private static void iastCallback(String other) { final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL; if (module != null) { diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/PathsCallSite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/PathsCallSite.java index 8bc531d3f77..020635feb4e 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/PathsCallSite.java +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/PathsCallSite.java @@ -35,6 +35,23 @@ public static void beforeGet(@CallSite.Argument @Nullable final URI uri) { } } + // Java 11+: Path.of — equivalent to Paths.get but defined on the Path interface + @CallSite.Before("java.nio.file.Path java.nio.file.Path.of(java.lang.String, java.lang.String[])") + public static void beforeOf( + @CallSite.Argument @Nullable final String first, + @CallSite.Argument @Nullable final String[] more) { + if (first != null && more != null) { + raspCallback(first, more); + } + } + + @CallSite.Before("java.nio.file.Path java.nio.file.Path.of(java.net.URI)") + public static void beforeOfUri(@CallSite.Argument @Nullable final URI uri) { + if (uri != null) { + raspCallback(uri); + } + } + private static void iastCallback(URI uri) { final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL; if (module != null) { diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/RandomAccessFileCallSite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/RandomAccessFileCallSite.java new file mode 100644 index 00000000000..a123ef89ea8 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/RandomAccessFileCallSite.java @@ -0,0 +1,30 @@ +package datadog.trace.instrumentation.java.lang; + +import datadog.trace.agent.tooling.csi.CallSite; +import datadog.trace.api.appsec.RaspCallSites; +import java.io.File; +import javax.annotation.Nullable; + +@CallSite( + spi = {RaspCallSites.class}, + helpers = FileIORaspHelper.class) +public class RandomAccessFileCallSite { + + @CallSite.Before("void java.io.RandomAccessFile.(java.lang.String, java.lang.String)") + public static void beforeConstructor( + @CallSite.Argument(0) @Nullable final String name, + @CallSite.Argument(1) @Nullable final String mode) { + if (name != null && mode != null) { + FileIORaspHelper.INSTANCE.beforeRandomAccessFileOpened(name, mode); + } + } + + @CallSite.Before("void java.io.RandomAccessFile.(java.io.File, java.lang.String)") + public static void beforeConstructorFile( + @CallSite.Argument(0) @Nullable final File file, + @CallSite.Argument(1) @Nullable final String mode) { + if (file != null && mode != null) { + FileIORaspHelper.INSTANCE.beforeRandomAccessFileOpened(file.getPath(), mode); + } + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileChannelCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileChannelCallSiteTest.groovy new file mode 100644 index 00000000000..1fd8973df4a --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileChannelCallSiteTest.groovy @@ -0,0 +1,52 @@ +package datadog.trace.instrumentation.java.io + +import datadog.trace.instrumentation.java.lang.FileIORaspHelper +import foo.bar.TestFileChannelSuite + +import java.nio.file.StandardOpenOption + +class FileChannelCallSiteTest extends BaseIoRaspCallSiteTest { + + void 'test RASP FileChannel.open read-only fires beforeFileLoaded'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_fc_read.txt').toPath() + + when: + TestFileChannelSuite.openRead(path).close() + + then: + 1 * helper.beforeFileLoaded(path.toString()) + 1 * helper.beforeFileWritten(path.toString()) + } + + void 'test RASP FileChannel.open write fires beforeFileWritten'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = temporaryFolder.resolve('test_rasp_fc_write.txt') + + when: + TestFileChannelSuite.openWrite(path).close() + + then: + 1 * helper.beforeFileLoaded(path.toString()) + 1 * helper.beforeFileWritten(path.toString()) + } + + void 'test RASP FileChannel.open with Set of options'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_fc_set.txt').toPath() + final options = EnumSet.of(StandardOpenOption.READ) + + when: + TestFileChannelSuite.openWithSet(path, options).close() + + then: + 1 * helper.beforeFileLoaded(path.toString()) + 1 * helper.beforeFileWritten(path.toString()) + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileReaderCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileReaderCallSiteTest.groovy new file mode 100644 index 00000000000..d6b2a014287 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileReaderCallSiteTest.groovy @@ -0,0 +1,33 @@ +package datadog.trace.instrumentation.java.io + +import datadog.trace.instrumentation.java.lang.FileIORaspHelper +import foo.bar.TestFileReaderSuite + +class FileReaderCallSiteTest extends BaseIoRaspCallSiteTest { + + void 'test RASP new file reader with path'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_reader.txt').toString() + + when: + TestFileReaderSuite.newFileReader(path) + + then: + 1 * helper.beforeFileLoaded(path) + } + + void 'test RASP new file reader with file'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_reader_file.txt') + + when: + TestFileReaderSuite.newFileReader(file) + + then: + 1 * helper.beforeFileLoaded(file.path) + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileWriterCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileWriterCallSiteTest.groovy new file mode 100644 index 00000000000..802b2d74fcb --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileWriterCallSiteTest.groovy @@ -0,0 +1,61 @@ +package datadog.trace.instrumentation.java.io + +import datadog.trace.instrumentation.java.lang.FileIORaspHelper +import foo.bar.TestFileWriterSuite +import groovy.transform.CompileDynamic + +@CompileDynamic +class FileWriterCallSiteTest extends BaseIoRaspCallSiteTest { + + void 'test RASP new file writer with path'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_writer_1.txt').toString() + + when: + TestFileWriterSuite.newFileWriter(path) + + then: + 1 * helper.beforeFileWritten(path) + } + + void 'test RASP new file writer with path and append'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_writer_2.txt').toString() + + when: + TestFileWriterSuite.newFileWriter(path, false) + + then: + 1 * helper.beforeFileWritten(path) + } + + void 'test RASP new file writer with file'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_writer_file_1.txt') + + when: + TestFileWriterSuite.newFileWriter(file) + + then: + 1 * helper.beforeFileWritten(file.path) + } + + void 'test RASP new file writer with file and append'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_writer_file_2.txt') + + when: + TestFileWriterSuite.newFileWriter(file, false) + + then: + 1 * helper.beforeFileWritten(file.path) + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FilesCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FilesCallSiteTest.groovy new file mode 100644 index 00000000000..fab9fa71bd0 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FilesCallSiteTest.groovy @@ -0,0 +1,225 @@ +package datadog.trace.instrumentation.java.io + +import datadog.trace.instrumentation.java.lang.FileIORaspHelper +import foo.bar.TestFilesSuite +import groovy.transform.CompileDynamic + +import java.nio.charset.StandardCharsets +import java.nio.file.StandardCopyOption + +@CompileDynamic +class FilesCallSiteTest extends BaseIoRaspCallSiteTest { + + // ===================== WRITE ===================== + + void 'test RASP Files.newOutputStream'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = temporaryFolder.resolve('test_rasp_newoutputstream.txt') + + when: + TestFilesSuite.newOutputStream(path).close() + + then: + 1 * helper.beforeFileWritten(path.toString()) + } + + void 'test RASP Files.copy from InputStream'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final target = temporaryFolder.resolve('test_rasp_copy_target.txt') + + when: + TestFilesSuite.copyFromStream(new ByteArrayInputStream(new byte[0]), target) + + then: + 1 * helper.beforeFileWritten(target.toString()) + } + + void 'test RASP Files.write byte array'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = temporaryFolder.resolve('test_rasp_write_bytes.txt') + + when: + TestFilesSuite.write(path, 'hello'.bytes) + + then: + 1 * helper.beforeFileWritten(path.toString()) + } + + void 'test RASP Files.write lines with charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = temporaryFolder.resolve('test_rasp_write_lines_cs.txt') + + when: + TestFilesSuite.writeLines(path, ['line1'], StandardCharsets.UTF_8) + + then: + 1 * helper.beforeFileWritten(path.toString()) + } + + void 'test RASP Files.write lines default charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = temporaryFolder.resolve('test_rasp_write_lines.txt') + + when: + TestFilesSuite.writeLinesDefaultCharset(path, ['line1']) + + then: + 1 * helper.beforeFileWritten(path.toString()) + } + + void 'test RASP Files.newBufferedWriter with charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = temporaryFolder.resolve('test_rasp_bw_cs.txt') + + when: + TestFilesSuite.newBufferedWriter(path, StandardCharsets.UTF_8).close() + + then: + 1 * helper.beforeFileWritten(path.toString()) + } + + void 'test RASP Files.newBufferedWriter default charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = temporaryFolder.resolve('test_rasp_bw.txt') + + when: + TestFilesSuite.newBufferedWriterDefaultCharset(path).close() + + then: + 1 * helper.beforeFileWritten(path.toString()) + } + + void 'test RASP Files.move'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final source = newFile('test_rasp_move_src.txt').toPath() + final target = temporaryFolder.resolve('test_rasp_move_dst.txt') + + when: + TestFilesSuite.move(source, target) + + then: + 1 * helper.beforeFileWritten(target.toString()) + } + + // ===================== READ ===================== + + void 'test RASP Files.newInputStream'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_newinputstream.txt').toPath() + + when: + TestFilesSuite.newInputStream(path).close() + + then: + 1 * helper.beforeFileLoaded(path.toString()) + } + + void 'test RASP Files.readAllBytes'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_readallbytes.txt').toPath() + + when: + TestFilesSuite.readAllBytes(path) + + then: + 1 * helper.beforeFileLoaded(path.toString()) + } + + void 'test RASP Files.readAllLines with charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_readlines_cs.txt').toPath() + + when: + TestFilesSuite.readAllLines(path, StandardCharsets.UTF_8) + + then: + 1 * helper.beforeFileLoaded(path.toString()) + } + + void 'test RASP Files.readAllLines default charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_readlines.txt').toPath() + + when: + TestFilesSuite.readAllLinesDefaultCharset(path) + + then: + 1 * helper.beforeFileLoaded(path.toString()) + } + + void 'test RASP Files.newBufferedReader with charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_br_cs.txt').toPath() + + when: + TestFilesSuite.newBufferedReader(path, StandardCharsets.UTF_8).close() + + then: + 1 * helper.beforeFileLoaded(path.toString()) + } + + void 'test RASP Files.newBufferedReader default charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_br.txt').toPath() + + when: + TestFilesSuite.newBufferedReaderDefaultCharset(path).close() + + then: + 1 * helper.beforeFileLoaded(path.toString()) + } + + void 'test RASP Files.lines with charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_lines_cs.txt').toPath() + + when: + TestFilesSuite.lines(path, StandardCharsets.UTF_8).close() + + then: + 1 * helper.beforeFileLoaded(path.toString()) + } + + void 'test RASP Files.lines default charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_lines.txt').toPath() + + when: + TestFilesSuite.linesDefaultCharset(path).close() + + then: + 1 * helper.beforeFileLoaded(path.toString()) + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/PathCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/PathCallSiteTest.groovy index 0527a0571fa..eee66dda9d6 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/PathCallSiteTest.groovy +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/PathCallSiteTest.groovy @@ -60,4 +60,32 @@ class PathCallSiteTest extends BaseIoRaspCallSiteTest { then: 1 * helper.beforeFileLoaded(path) } + + void 'test RASP resolve with Path argument'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final parent = getRootFolder() + final other = newFile('test_rasp_path_resolve.txt').toPath() + + when: + TestPathSuite.resolveWithPath(parent, other) + + then: + 1 * helper.beforeFileLoaded(other.toString()) + } + + void 'test RASP resolveSibling with Path argument'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final sibling = newFile('test_rasp_path_sibling_1.txt').toPath() + final other = newFile('test_rasp_path_sibling_2.txt').toPath() + + when: + TestPathSuite.resolveSiblingWithPath(sibling, other) + + then: + 1 * helper.beforeFileLoaded(other.toString()) + } } diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/PathsCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/PathsCallSiteTest.groovy index 786b84e51df..eb6e6ceba72 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/PathsCallSiteTest.groovy +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/PathsCallSiteTest.groovy @@ -66,4 +66,5 @@ class PathsCallSiteTest extends BaseIoRaspCallSiteTest { then: 1 * helper.beforeFileLoaded(file) } + } diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/RandomAccessFileCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/RandomAccessFileCallSiteTest.groovy new file mode 100644 index 00000000000..15c923476c0 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/RandomAccessFileCallSiteTest.groovy @@ -0,0 +1,59 @@ +package datadog.trace.instrumentation.java.io + +import datadog.trace.instrumentation.java.lang.FileIORaspHelper +import foo.bar.TestRandomAccessFileSuite + +class RandomAccessFileCallSiteTest extends BaseIoRaspCallSiteTest { + + void 'test RASP RandomAccessFile with String path read-only mode'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_raf_r.txt') + + when: + TestRandomAccessFileSuite.newRandomAccessFile(file.path, 'r') + + then: + 1 * helper.beforeRandomAccessFileOpened(file.path, 'r') + } + + void 'test RASP RandomAccessFile with String path read-write mode'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_raf_rw.txt') + + when: + TestRandomAccessFileSuite.newRandomAccessFile(file.path, 'rw') + + then: + 1 * helper.beforeRandomAccessFileOpened(file.path, 'rw') + } + + void 'test RASP RandomAccessFile with File read-only mode'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_raf_file_r.txt') + + when: + TestRandomAccessFileSuite.newRandomAccessFile(file, 'r') + + then: + 1 * helper.beforeRandomAccessFileOpened(file.path, 'r') + } + + void 'test RASP RandomAccessFile with File read-write mode'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_raf_file_rw.txt') + + when: + TestRandomAccessFileSuite.newRandomAccessFile(file, 'rw') + + then: + 1 * helper.beforeRandomAccessFileOpened(file.path, 'rw') + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileChannelSuite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileChannelSuite.java new file mode 100644 index 00000000000..1f5b9feb113 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileChannelSuite.java @@ -0,0 +1,35 @@ +package foo.bar; + +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.FileAttribute; +import java.util.Set; + +public class TestFileChannelSuite { + + public static FileChannel openRead(final Path path) throws IOException { + return FileChannel.open(path, StandardOpenOption.READ); + } + + public static FileChannel openWrite(final Path path) throws IOException { + return FileChannel.open( + path, + StandardOpenOption.WRITE, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + } + + public static FileChannel openWithOptions(final Path path, final OpenOption... options) + throws IOException { + return FileChannel.open(path, options); + } + + public static FileChannel openWithSet( + final Path path, final Set options, final FileAttribute... attrs) + throws IOException { + return FileChannel.open(path, options, attrs); + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileReaderSuite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileReaderSuite.java new file mode 100644 index 00000000000..4ba3c660dfb --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileReaderSuite.java @@ -0,0 +1,16 @@ +package foo.bar; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +public class TestFileReaderSuite { + + public static FileReader newFileReader(final String path) throws IOException { + return new FileReader(path); + } + + public static FileReader newFileReader(final File file) throws IOException { + return new FileReader(file); + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileWriterSuite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileWriterSuite.java new file mode 100644 index 00000000000..8c528d0adaa --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFileWriterSuite.java @@ -0,0 +1,25 @@ +package foo.bar; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +public class TestFileWriterSuite { + + public static FileWriter newFileWriter(final String path) throws IOException { + return new FileWriter(path); + } + + public static FileWriter newFileWriter(final String path, final boolean append) + throws IOException { + return new FileWriter(path, append); + } + + public static FileWriter newFileWriter(final File file) throws IOException { + return new FileWriter(file); + } + + public static FileWriter newFileWriter(final File file, final boolean append) throws IOException { + return new FileWriter(file, append); + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFilesSuite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFilesSuite.java new file mode 100644 index 00000000000..7101dbbdfa2 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestFilesSuite.java @@ -0,0 +1,100 @@ +package foo.bar; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.file.CopyOption; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Stream; + +public class TestFilesSuite { + + // ===================== WRITE ===================== + + public static OutputStream newOutputStream(final Path path, final OpenOption... options) + throws IOException { + return Files.newOutputStream(path, options); + } + + public static long copyFromStream( + final InputStream in, final Path target, final CopyOption... options) throws IOException { + return Files.copy(in, target, options); + } + + public static Path write(final Path path, final byte[] bytes, final OpenOption... options) + throws IOException { + return Files.write(path, bytes, options); + } + + public static Path writeLines( + final Path path, + final Iterable lines, + final Charset cs, + final OpenOption... options) + throws IOException { + return Files.write(path, lines, cs, options); + } + + public static Path writeLinesDefaultCharset( + final Path path, final Iterable lines, final OpenOption... options) + throws IOException { + return Files.write(path, lines, options); + } + + public static BufferedWriter newBufferedWriter( + final Path path, final Charset cs, final OpenOption... options) throws IOException { + return Files.newBufferedWriter(path, cs, options); + } + + public static BufferedWriter newBufferedWriterDefaultCharset( + final Path path, final OpenOption... options) throws IOException { + return Files.newBufferedWriter(path, options); + } + + public static Path move(final Path source, final Path target, final CopyOption... options) + throws IOException { + return Files.move(source, target); + } + + // ===================== READ ===================== + + public static InputStream newInputStream(final Path path, final OpenOption... options) + throws IOException { + return Files.newInputStream(path, options); + } + + public static byte[] readAllBytes(final Path path) throws IOException { + return Files.readAllBytes(path); + } + + public static List readAllLines(final Path path, final Charset cs) throws IOException { + return Files.readAllLines(path, cs); + } + + public static List readAllLinesDefaultCharset(final Path path) throws IOException { + return Files.readAllLines(path); + } + + public static BufferedReader newBufferedReader(final Path path, final Charset cs) + throws IOException { + return Files.newBufferedReader(path, cs); + } + + public static BufferedReader newBufferedReaderDefaultCharset(final Path path) throws IOException { + return Files.newBufferedReader(path); + } + + public static Stream lines(final Path path, final Charset cs) throws IOException { + return Files.lines(path, cs); + } + + public static Stream linesDefaultCharset(final Path path) throws IOException { + return Files.lines(path); + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestPathSuite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestPathSuite.java index 3d3ad605ce8..e9f7a1dc0e1 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestPathSuite.java +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestPathSuite.java @@ -11,4 +11,12 @@ public static Path resolve(final Path parent, final String other) { public static Path resolveSibling(final Path parent, final String other) { return parent.resolveSibling(other); } + + public static Path resolveWithPath(final Path parent, final Path other) { + return parent.resolve(other); + } + + public static Path resolveSiblingWithPath(final Path sibling, final Path other) { + return sibling.resolveSibling(other); + } } diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestRandomAccessFileSuite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestRandomAccessFileSuite.java new file mode 100644 index 00000000000..e743993adee --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/java/foo/bar/TestRandomAccessFileSuite.java @@ -0,0 +1,18 @@ +package foo.bar; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; + +public class TestRandomAccessFileSuite { + + public static RandomAccessFile newRandomAccessFile(final String name, final String mode) + throws IOException { + return new RandomAccessFile(name, mode); + } + + public static RandomAccessFile newRandomAccessFile(final File file, final String mode) + throws IOException { + return new RandomAccessFile(file, mode); + } +} From 6a35cf4b42ced8ae956de822cc39a6f12e01cb74 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 15 Apr 2026 10:14:06 +0200 Subject: [PATCH 3/6] Apply spotless formatting --- .../trace/instrumentation/java/io/PathsCallSiteTest.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/PathsCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/PathsCallSiteTest.groovy index eb6e6ceba72..786b84e51df 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/PathsCallSiteTest.groovy +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/PathsCallSiteTest.groovy @@ -66,5 +66,4 @@ class PathsCallSiteTest extends BaseIoRaspCallSiteTest { then: 1 * helper.beforeFileLoaded(file) } - } From 2623f4780fe795335c2c7aceb17a92bc02e309ac Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 15 Apr 2026 10:32:38 +0200 Subject: [PATCH 4/6] Add Java 11 test suite for RASP coverage of Java 11 file I/O APIs Adds a java11Test source set that compiles with --release 11 and runs only on JDK 11+. Tests cover the Java 11-only overloads that were instrumented but previously untestable from Java 8 sources: - FileReader(String/File, Charset) constructors - FileWriter(String/File, Charset[, boolean]) constructors - Files.writeString(Path, CharSequence, [Charset,] OpenOption...) - Files.readString(Path[, Charset]) - Path.of(String, String[]) and Path.of(URI) static methods Build configuration uses ext.java11TestMinJavaVersionForTests so the testJvmConstraints plugin skips the suite on pre-11 JVMs. --- .../java/java-io-1.8/build.gradle | 12 ++++ .../io/FileReaderCharsetCallSiteTest.groovy | 35 +++++++++++ .../io/FileWriterCharsetCallSiteTest.groovy | 61 +++++++++++++++++++ .../java/io/FilesJava11CallSiteTest.groovy | 61 +++++++++++++++++++ .../java/io/PathOfCallSiteTest.groovy | 37 +++++++++++ .../foo/bar/TestFileReaderCharsetSuite.java | 19 ++++++ .../foo/bar/TestFileWriterCharsetSuite.java | 29 +++++++++ .../java/foo/bar/TestFilesJava11Suite.java | 32 ++++++++++ .../java/foo/bar/TestPathOfSuite.java | 15 +++++ 9 files changed, 301 insertions(+) create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/FileReaderCharsetCallSiteTest.groovy create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/FileWriterCharsetCallSiteTest.groovy create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/FilesJava11CallSiteTest.groovy create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/PathOfCallSiteTest.groovy create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestFileReaderCharsetSuite.java create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestFileWriterCharsetSuite.java create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestFilesJava11Suite.java create mode 100644 dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestPathOfSuite.java diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/build.gradle b/dd-java-agent/instrumentation/java/java-io-1.8/build.gradle index 0606b2c3618..c56f4f97d8b 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/build.gradle +++ b/dd-java-agent/instrumentation/java/java-io-1.8/build.gradle @@ -8,8 +8,20 @@ apply from: "$rootDir/gradle/java.gradle" apply plugin: 'dd-trace-java.call-site-instrumentation' addTestSuiteForDir('latestDepTest', 'test') +addTestSuiteForDir('java11Test', 'java11Test') + +// java11Test only runs on JDK 11+; the testJvmConstraints plugin reads this property by convention +ext.java11TestMinJavaVersionForTests = JavaVersion.VERSION_11 + +tasks.named("compileJava11TestJava", JavaCompile) { + configureCompiler(it, 11) +} +tasks.named("compileJava11TestGroovy", GroovyCompile) { + configureCompiler(it, 11) +} dependencies { testRuntimeOnly project(':dd-java-agent:instrumentation:datadog:asm:iast-instrumenter') testImplementation group: 'org.apache.tomcat', name: 'tomcat-catalina', version: '9.0.56' + java11TestImplementation sourceSets.test.output } diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/FileReaderCharsetCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/FileReaderCharsetCallSiteTest.groovy new file mode 100644 index 00000000000..1212f5d4756 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/FileReaderCharsetCallSiteTest.groovy @@ -0,0 +1,35 @@ +package datadog.trace.instrumentation.java.io + +import datadog.trace.instrumentation.java.lang.FileIORaspHelper +import foo.bar.TestFileReaderCharsetSuite + +import java.nio.charset.Charset + +class FileReaderCharsetCallSiteTest extends BaseIoRaspCallSiteTest { + + void 'test RASP new FileReader with String path and Charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_fr_str_cs.txt') + + when: + TestFileReaderCharsetSuite.newFileReader(file.path, Charset.defaultCharset()).close() + + then: + 1 * helper.beforeFileLoaded(file.path) + } + + void 'test RASP new FileReader with File and Charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_fr_file_cs.txt') + + when: + TestFileReaderCharsetSuite.newFileReader(file, Charset.defaultCharset()).close() + + then: + 1 * helper.beforeFileLoaded(file.path) + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/FileWriterCharsetCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/FileWriterCharsetCallSiteTest.groovy new file mode 100644 index 00000000000..24e4a4a304b --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/FileWriterCharsetCallSiteTest.groovy @@ -0,0 +1,61 @@ +package datadog.trace.instrumentation.java.io + +import datadog.trace.instrumentation.java.lang.FileIORaspHelper +import foo.bar.TestFileWriterCharsetSuite + +import java.nio.charset.Charset + +class FileWriterCharsetCallSiteTest extends BaseIoRaspCallSiteTest { + + void 'test RASP new FileWriter with String path and Charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_fw_str_cs.txt') + + when: + TestFileWriterCharsetSuite.newFileWriter(file.path, Charset.defaultCharset()).close() + + then: + 1 * helper.beforeFileWritten(file.path) + } + + void 'test RASP new FileWriter with String path, Charset, and append flag'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_fw_str_cs_append.txt') + + when: + TestFileWriterCharsetSuite.newFileWriter(file.path, Charset.defaultCharset(), false).close() + + then: + 1 * helper.beforeFileWritten(file.path) + } + + void 'test RASP new FileWriter with File and Charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_fw_file_cs.txt') + + when: + TestFileWriterCharsetSuite.newFileWriter(file, Charset.defaultCharset()).close() + + then: + 1 * helper.beforeFileWritten(file.path) + } + + void 'test RASP new FileWriter with File, Charset, and append flag'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final file = newFile('test_rasp_fw_file_cs_append.txt') + + when: + TestFileWriterCharsetSuite.newFileWriter(file, Charset.defaultCharset(), false).close() + + then: + 1 * helper.beforeFileWritten(file.path) + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/FilesJava11CallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/FilesJava11CallSiteTest.groovy new file mode 100644 index 00000000000..64991d990ca --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/FilesJava11CallSiteTest.groovy @@ -0,0 +1,61 @@ +package datadog.trace.instrumentation.java.io + +import datadog.trace.instrumentation.java.lang.FileIORaspHelper +import foo.bar.TestFilesJava11Suite + +import java.nio.charset.StandardCharsets + +class FilesJava11CallSiteTest extends BaseIoRaspCallSiteTest { + + void 'test RASP Files.writeString without charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = temporaryFolder.resolve('test_rasp_writestring.txt') + + when: + TestFilesJava11Suite.writeString(path, 'hello') + + then: + 1 * helper.beforeFileWritten(path.toString()) + } + + void 'test RASP Files.writeString with charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = temporaryFolder.resolve('test_rasp_writestring_cs.txt') + + when: + TestFilesJava11Suite.writeString(path, 'hello', StandardCharsets.UTF_8) + + then: + 1 * helper.beforeFileWritten(path.toString()) + } + + void 'test RASP Files.readString without charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_readstring.txt').toPath() + + when: + TestFilesJava11Suite.readString(path) + + then: + 1 * helper.beforeFileLoaded(path.toString()) + } + + void 'test RASP Files.readString with charset'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_readstring_cs.txt').toPath() + + when: + TestFilesJava11Suite.readString(path, StandardCharsets.UTF_8) + + then: + 1 * helper.beforeFileLoaded(path.toString()) + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/PathOfCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/PathOfCallSiteTest.groovy new file mode 100644 index 00000000000..495d54b3b6e --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/groovy/datadog/trace/instrumentation/java/io/PathOfCallSiteTest.groovy @@ -0,0 +1,37 @@ +package datadog.trace.instrumentation.java.io + +import datadog.trace.instrumentation.java.lang.FileIORaspHelper +import foo.bar.TestPathOfSuite + +class PathOfCallSiteTest extends BaseIoRaspCallSiteTest { + + void 'test RASP Path.of from strings'(final String first, final String... more) { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + + when: + TestPathOfSuite.of(first, more) + + then: + 1 * helper.beforeFileLoaded(first, more) + + where: + first | more + 'test.txt' | [] as String[] + '/tmp' | ['log', 'test.txt'] as String[] + } + + void 'test RASP Path.of from URI'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final uri = new URI('file:/test.txt') + + when: + TestPathOfSuite.of(uri) + + then: + 1 * helper.beforeFileLoaded(uri) + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestFileReaderCharsetSuite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestFileReaderCharsetSuite.java new file mode 100644 index 00000000000..d504657f80b --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestFileReaderCharsetSuite.java @@ -0,0 +1,19 @@ +package foo.bar; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.Charset; + +public class TestFileReaderCharsetSuite { + + public static FileReader newFileReader(final String path, final Charset charset) + throws IOException { + return new FileReader(path, charset); + } + + public static FileReader newFileReader(final File file, final Charset charset) + throws IOException { + return new FileReader(file, charset); + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestFileWriterCharsetSuite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestFileWriterCharsetSuite.java new file mode 100644 index 00000000000..ae91aa3fb27 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestFileWriterCharsetSuite.java @@ -0,0 +1,29 @@ +package foo.bar; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.Charset; + +public class TestFileWriterCharsetSuite { + + public static FileWriter newFileWriter(final String path, final Charset charset) + throws IOException { + return new FileWriter(path, charset); + } + + public static FileWriter newFileWriter( + final String path, final Charset charset, final boolean append) throws IOException { + return new FileWriter(path, charset, append); + } + + public static FileWriter newFileWriter(final File file, final Charset charset) + throws IOException { + return new FileWriter(file, charset); + } + + public static FileWriter newFileWriter( + final File file, final Charset charset, final boolean append) throws IOException { + return new FileWriter(file, charset, append); + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestFilesJava11Suite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestFilesJava11Suite.java new file mode 100644 index 00000000000..af283fa8cf6 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestFilesJava11Suite.java @@ -0,0 +1,32 @@ +package foo.bar; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; + +public class TestFilesJava11Suite { + + public static Path writeString( + final Path path, final CharSequence content, final OpenOption... options) throws IOException { + return Files.writeString(path, content, options); + } + + public static Path writeString( + final Path path, + final CharSequence content, + final Charset charset, + final OpenOption... options) + throws IOException { + return Files.writeString(path, content, charset, options); + } + + public static String readString(final Path path) throws IOException { + return Files.readString(path); + } + + public static String readString(final Path path, final Charset charset) throws IOException { + return Files.readString(path, charset); + } +} diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestPathOfSuite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestPathOfSuite.java new file mode 100644 index 00000000000..9c2f2b8cb09 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/java11Test/java/foo/bar/TestPathOfSuite.java @@ -0,0 +1,15 @@ +package foo.bar; + +import java.net.URI; +import java.nio.file.Path; + +public class TestPathOfSuite { + + public static Path of(final String first, final String... more) { + return Path.of(first, more); + } + + public static Path of(final URI uri) { + return Path.of(uri); + } +} From 468e83facb8c2dd8bf57d675d85275a2d45603ab Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 15 Apr 2026 10:52:46 +0200 Subject: [PATCH 5/6] Remove unused StandardCopyOption import from FilesCallSiteTest --- .../trace/instrumentation/java/io/FilesCallSiteTest.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FilesCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FilesCallSiteTest.groovy index fab9fa71bd0..f91516c59f7 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FilesCallSiteTest.groovy +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FilesCallSiteTest.groovy @@ -5,7 +5,6 @@ import foo.bar.TestFilesSuite import groovy.transform.CompileDynamic import java.nio.charset.StandardCharsets -import java.nio.file.StandardCopyOption @CompileDynamic class FilesCallSiteTest extends BaseIoRaspCallSiteTest { From a66eb337bfa3974a0e9699fd117d993a38cda212 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 15 Apr 2026 17:44:24 +0200 Subject: [PATCH 6/6] fix(appsec): gate FileChannel write callback on write-capable open options FileChannel.open() with READ-only options was incorrectly triggering the fileWritten callback, which could cause false positives in the zipslip rule (dog-920-110) when a read-only channel open with a traversal path coincided with a multipart zip upload in the same request. Split beforeOpen into two overload-specific methods so the OpenOption arguments can be inspected at the call site, mirroring the existing pattern in beforeRandomAccessFileOpened. Also fix a latent bug in AdviceGeneratorImpl: .sorted() without a comparator on ArgumentSpecification (which does not implement Comparable) would ClassCastException when an advice method captures a strict subset of a target method's arguments. Fixed with Comparator.comparingInt. --- .../plugin/csi/impl/AdviceGeneratorImpl.java | 3 +- .../java/lang/FileChannelCallSite.java | 60 +++++++++++++++++-- .../java/io/FileChannelCallSiteTest.groovy | 37 ++++++++++-- 3 files changed, 90 insertions(+), 10 deletions(-) diff --git a/buildSrc/call-site-instrumentation-plugin/src/main/java/datadog/trace/plugin/csi/impl/AdviceGeneratorImpl.java b/buildSrc/call-site-instrumentation-plugin/src/main/java/datadog/trace/plugin/csi/impl/AdviceGeneratorImpl.java index 14efc4a42d4..e40fa10993e 100644 --- a/buildSrc/call-site-instrumentation-plugin/src/main/java/datadog/trace/plugin/csi/impl/AdviceGeneratorImpl.java +++ b/buildSrc/call-site-instrumentation-plugin/src/main/java/datadog/trace/plugin/csi/impl/AdviceGeneratorImpl.java @@ -58,6 +58,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -266,7 +267,7 @@ private static void writeStackOperations(final AdviceSpecification advice, final final List parameterIndicesValues = advice .getArguments() - .sorted() + .sorted(Comparator.comparingInt(argSpec -> argSpec.getIndex())) .map(argSpec -> intLiteral(argSpec.getIndex())) .collect(Collectors.toList()); final VariableDeclarator parameterIndices = diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileChannelCallSite.java b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileChannelCallSite.java index 3538885208d..9b5b04fe8b1 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileChannelCallSite.java +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileChannelCallSite.java @@ -2,7 +2,10 @@ import datadog.trace.agent.tooling.csi.CallSite; import datadog.trace.api.appsec.RaspCallSites; +import java.nio.file.OpenOption; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Set; import javax.annotation.Nullable; @CallSite( @@ -12,14 +15,61 @@ public class FileChannelCallSite { @CallSite.Before( "java.nio.channels.FileChannel java.nio.channels.FileChannel.open(java.nio.file.Path, java.nio.file.OpenOption[])") + public static void beforeOpenArray( + @CallSite.Argument(0) @Nullable final Path path, + @CallSite.Argument(1) @Nullable final OpenOption[] options) { + if (path != null) { + String pathStr = path.toString(); + FileIORaspHelper.INSTANCE.beforeFileLoaded(pathStr); + if (hasWriteOption(options)) { + FileIORaspHelper.INSTANCE.beforeFileWritten(pathStr); + } + } + } + @CallSite.Before( "java.nio.channels.FileChannel java.nio.channels.FileChannel.open(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])") - public static void beforeOpen(@CallSite.Argument(0) @Nullable final Path path) { + public static void beforeOpenSet( + @CallSite.Argument(0) @Nullable final Path path, + @CallSite.Argument(1) @Nullable final Set options) { if (path != null) { - // Fire both read and write callbacks: the WAF determines whether to block - // based on the full request context (e.g. zipslip requires body.filenames too). - FileIORaspHelper.INSTANCE.beforeFileLoaded(path.toString()); - FileIORaspHelper.INSTANCE.beforeFileWritten(path.toString()); + String pathStr = path.toString(); + FileIORaspHelper.INSTANCE.beforeFileLoaded(pathStr); + if (hasWriteOption(options)) { + FileIORaspHelper.INSTANCE.beforeFileWritten(pathStr); + } + } + } + + private static boolean hasWriteOption(@Nullable final OpenOption[] options) { + if (options == null) { + return false; } + for (OpenOption opt : options) { + if (isWriteOption(opt)) { + return true; + } + } + return false; + } + + private static boolean hasWriteOption(@Nullable final Set options) { + if (options == null) { + return false; + } + for (OpenOption opt : options) { + if (isWriteOption(opt)) { + return true; + } + } + return false; + } + + private static boolean isWriteOption(final OpenOption opt) { + return opt == StandardOpenOption.WRITE + || opt == StandardOpenOption.APPEND + || opt == StandardOpenOption.CREATE + || opt == StandardOpenOption.CREATE_NEW + || opt == StandardOpenOption.TRUNCATE_EXISTING; } } diff --git a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileChannelCallSiteTest.groovy b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileChannelCallSiteTest.groovy index 1fd8973df4a..3f53551c688 100644 --- a/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileChannelCallSiteTest.groovy +++ b/dd-java-agent/instrumentation/java/java-io-1.8/src/test/groovy/datadog/trace/instrumentation/java/io/FileChannelCallSiteTest.groovy @@ -7,7 +7,7 @@ import java.nio.file.StandardOpenOption class FileChannelCallSiteTest extends BaseIoRaspCallSiteTest { - void 'test RASP FileChannel.open read-only fires beforeFileLoaded'() { + void 'test RASP FileChannel.open read-only fires only beforeFileLoaded'() { setup: final helper = Mock(FileIORaspHelper) FileIORaspHelper.INSTANCE = helper @@ -18,10 +18,10 @@ class FileChannelCallSiteTest extends BaseIoRaspCallSiteTest { then: 1 * helper.beforeFileLoaded(path.toString()) - 1 * helper.beforeFileWritten(path.toString()) + 0 * helper.beforeFileWritten(_) } - void 'test RASP FileChannel.open write fires beforeFileWritten'() { + void 'test RASP FileChannel.open write fires both beforeFileLoaded and beforeFileWritten'() { setup: final helper = Mock(FileIORaspHelper) FileIORaspHelper.INSTANCE = helper @@ -35,7 +35,7 @@ class FileChannelCallSiteTest extends BaseIoRaspCallSiteTest { 1 * helper.beforeFileWritten(path.toString()) } - void 'test RASP FileChannel.open with Set of options'() { + void 'test RASP FileChannel.open with Set READ-only fires only beforeFileLoaded'() { setup: final helper = Mock(FileIORaspHelper) FileIORaspHelper.INSTANCE = helper @@ -45,8 +45,37 @@ class FileChannelCallSiteTest extends BaseIoRaspCallSiteTest { when: TestFileChannelSuite.openWithSet(path, options).close() + then: + 1 * helper.beforeFileLoaded(path.toString()) + 0 * helper.beforeFileWritten(_) + } + + void 'test RASP FileChannel.open with Set WRITE fires both callbacks'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = temporaryFolder.resolve('test_rasp_fc_set_write.txt') + final options = EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE) + + when: + TestFileChannelSuite.openWithSet(path, options).close() + then: 1 * helper.beforeFileLoaded(path.toString()) 1 * helper.beforeFileWritten(path.toString()) } + + void 'test RASP FileChannel.open with no options fires only beforeFileLoaded'() { + setup: + final helper = Mock(FileIORaspHelper) + FileIORaspHelper.INSTANCE = helper + final path = newFile('test_rasp_fc_default.txt').toPath() + + when: + TestFileChannelSuite.openWithOptions(path).close() + + then: + 1 * helper.beforeFileLoaded(path.toString()) + 0 * helper.beforeFileWritten(_) + } }