Skip to content

Commit b1e991e

Browse files
committed
feat(appsec): extend RASP file I/O coverage to FileReader, FileWriter, RandomAccessFile, Files.* and FileChannel
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
1 parent c6458e3 commit b1e991e

21 files changed

+906
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package datadog.trace.instrumentation.java.lang;
2+
3+
import datadog.trace.agent.tooling.csi.CallSite;
4+
import datadog.trace.api.appsec.RaspCallSites;
5+
import java.nio.file.Path;
6+
import javax.annotation.Nullable;
7+
8+
@CallSite(
9+
spi = {RaspCallSites.class},
10+
helpers = FileIORaspHelper.class)
11+
public class FileChannelCallSite {
12+
13+
@CallSite.Before(
14+
"java.nio.channels.FileChannel java.nio.channels.FileChannel.open(java.nio.file.Path, java.nio.file.OpenOption[])")
15+
@CallSite.Before(
16+
"java.nio.channels.FileChannel java.nio.channels.FileChannel.open(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])")
17+
public static void beforeOpen(@CallSite.Argument(0) @Nullable final Path path) {
18+
if (path != null) {
19+
// Fire both read and write callbacks: the WAF determines whether to block
20+
// based on the full request context (e.g. zipslip requires body.filenames too).
21+
FileIORaspHelper.INSTANCE.beforeFileLoaded(path.toString());
22+
FileIORaspHelper.INSTANCE.beforeFileWritten(path.toString());
23+
}
24+
}
25+
}

dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/FileIORaspHelper.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,18 @@ public void beforeFileWritten(@Nonnull final String path) {
101101
invokeRaspCallback(EVENTS.fileWritten(), path);
102102
}
103103

104+
/**
105+
* Fires {@link #beforeFileLoaded} for read modes ("r"), and both {@link #beforeFileLoaded} and
106+
* {@link #beforeFileWritten} for write modes ("rw", "rws", "rwd").
107+
*/
108+
public void beforeRandomAccessFileOpened(@Nonnull final String path, @Nonnull final String mode) {
109+
// "r" = read only; "rw", "rws", "rwd" = read + write
110+
beforeFileLoaded(path);
111+
if (mode.length() > 1) {
112+
beforeFileWritten(path);
113+
}
114+
}
115+
104116
private void invokeRaspCallback(
105117
EventType<BiFunction<RequestContext, String, Flow<Void>>> eventType,
106118
@Nonnull final String path) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package datadog.trace.instrumentation.java.lang;
2+
3+
import datadog.trace.agent.tooling.csi.CallSite;
4+
import datadog.trace.api.appsec.RaspCallSites;
5+
import java.io.File;
6+
import javax.annotation.Nullable;
7+
8+
@CallSite(
9+
spi = {RaspCallSites.class},
10+
helpers = FileIORaspHelper.class)
11+
public class FileReaderCallSite {
12+
13+
@CallSite.Before("void java.io.FileReader.<init>(java.lang.String)")
14+
// Java 11+: FileReader(String, Charset)
15+
@CallSite.Before("void java.io.FileReader.<init>(java.lang.String, java.nio.charset.Charset)")
16+
public static void beforeConstructor(@CallSite.Argument(0) @Nullable final String path) {
17+
if (path != null) {
18+
raspCallback(path);
19+
}
20+
}
21+
22+
@CallSite.Before("void java.io.FileReader.<init>(java.io.File)")
23+
// Java 11+: FileReader(File, Charset)
24+
@CallSite.Before("void java.io.FileReader.<init>(java.io.File, java.nio.charset.Charset)")
25+
public static void beforeConstructorFile(@CallSite.Argument(0) @Nullable final File file) {
26+
if (file != null) {
27+
raspCallback(file.getPath());
28+
}
29+
}
30+
31+
private static void raspCallback(final String path) {
32+
FileIORaspHelper.INSTANCE.beforeFileLoaded(path);
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package datadog.trace.instrumentation.java.lang;
2+
3+
import datadog.trace.agent.tooling.csi.CallSite;
4+
import datadog.trace.api.appsec.RaspCallSites;
5+
import java.io.File;
6+
import javax.annotation.Nullable;
7+
8+
@CallSite(
9+
spi = {RaspCallSites.class},
10+
helpers = FileIORaspHelper.class)
11+
public class FileWriterCallSite {
12+
13+
@CallSite.Before("void java.io.FileWriter.<init>(java.lang.String)")
14+
@CallSite.Before("void java.io.FileWriter.<init>(java.lang.String, boolean)")
15+
// Java 11+: FileWriter(String, Charset) and FileWriter(String, Charset, boolean)
16+
@CallSite.Before("void java.io.FileWriter.<init>(java.lang.String, java.nio.charset.Charset)")
17+
@CallSite.Before(
18+
"void java.io.FileWriter.<init>(java.lang.String, java.nio.charset.Charset, boolean)")
19+
public static void beforeConstructor(@CallSite.Argument(0) @Nullable final String path) {
20+
if (path != null) {
21+
raspCallback(path);
22+
}
23+
}
24+
25+
@CallSite.Before("void java.io.FileWriter.<init>(java.io.File)")
26+
@CallSite.Before("void java.io.FileWriter.<init>(java.io.File, boolean)")
27+
// Java 11+: FileWriter(File, Charset) and FileWriter(File, Charset, boolean)
28+
@CallSite.Before("void java.io.FileWriter.<init>(java.io.File, java.nio.charset.Charset)")
29+
@CallSite.Before(
30+
"void java.io.FileWriter.<init>(java.io.File, java.nio.charset.Charset, boolean)")
31+
public static void beforeConstructorFile(@CallSite.Argument(0) @Nullable final File file) {
32+
if (file != null) {
33+
raspCallback(file.getPath());
34+
}
35+
}
36+
37+
private static void raspCallback(final String path) {
38+
FileIORaspHelper.INSTANCE.beforeFileWritten(path);
39+
}
40+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package datadog.trace.instrumentation.java.lang;
2+
3+
import datadog.trace.agent.tooling.csi.CallSite;
4+
import datadog.trace.api.appsec.RaspCallSites;
5+
import java.nio.file.Path;
6+
import javax.annotation.Nullable;
7+
8+
@CallSite(
9+
spi = {RaspCallSites.class},
10+
helpers = FileIORaspHelper.class)
11+
public class FilesCallSite {
12+
13+
// ===================== WRITE =====================
14+
15+
@CallSite.Before(
16+
"java.io.OutputStream java.nio.file.Files.newOutputStream(java.nio.file.Path, java.nio.file.OpenOption[])")
17+
@CallSite.Before(
18+
"java.nio.file.Path java.nio.file.Files.write(java.nio.file.Path, byte[], java.nio.file.OpenOption[])")
19+
@CallSite.Before(
20+
"java.nio.file.Path java.nio.file.Files.write(java.nio.file.Path, java.lang.Iterable, java.nio.charset.Charset, java.nio.file.OpenOption[])")
21+
@CallSite.Before(
22+
"java.nio.file.Path java.nio.file.Files.write(java.nio.file.Path, java.lang.Iterable, java.nio.file.OpenOption[])")
23+
// Java 11+: Files.writeString variants
24+
@CallSite.Before(
25+
"java.nio.file.Path java.nio.file.Files.writeString(java.nio.file.Path, java.lang.CharSequence, java.nio.file.OpenOption[])")
26+
@CallSite.Before(
27+
"java.nio.file.Path java.nio.file.Files.writeString(java.nio.file.Path, java.lang.CharSequence, java.nio.charset.Charset, java.nio.file.OpenOption[])")
28+
@CallSite.Before(
29+
"java.io.BufferedWriter java.nio.file.Files.newBufferedWriter(java.nio.file.Path, java.nio.charset.Charset, java.nio.file.OpenOption[])")
30+
@CallSite.Before(
31+
"java.io.BufferedWriter java.nio.file.Files.newBufferedWriter(java.nio.file.Path, java.nio.file.OpenOption[])")
32+
public static void beforeWrite(@CallSite.Argument(0) @Nullable final Path path) {
33+
if (path != null) {
34+
FileIORaspHelper.INSTANCE.beforeFileWritten(path.toString());
35+
}
36+
}
37+
38+
@CallSite.Before(
39+
"long java.nio.file.Files.copy(java.io.InputStream, java.nio.file.Path, java.nio.file.CopyOption[])")
40+
public static void beforeCopyFromStream(@CallSite.Argument(1) @Nullable final Path target) {
41+
if (target != null) {
42+
FileIORaspHelper.INSTANCE.beforeFileWritten(target.toString());
43+
}
44+
}
45+
46+
@CallSite.Before(
47+
"java.nio.file.Path java.nio.file.Files.move(java.nio.file.Path, java.nio.file.Path, java.nio.file.CopyOption[])")
48+
public static void beforeMove(@CallSite.Argument(1) @Nullable final Path target) {
49+
if (target != null) {
50+
FileIORaspHelper.INSTANCE.beforeFileWritten(target.toString());
51+
}
52+
}
53+
54+
// ===================== READ =====================
55+
56+
@CallSite.Before(
57+
"java.io.InputStream java.nio.file.Files.newInputStream(java.nio.file.Path, java.nio.file.OpenOption[])")
58+
@CallSite.Before("byte[] java.nio.file.Files.readAllBytes(java.nio.file.Path)")
59+
@CallSite.Before(
60+
"java.util.List java.nio.file.Files.readAllLines(java.nio.file.Path, java.nio.charset.Charset)")
61+
@CallSite.Before("java.util.List java.nio.file.Files.readAllLines(java.nio.file.Path)")
62+
// Java 11+: Files.readString variants
63+
@CallSite.Before("java.lang.String java.nio.file.Files.readString(java.nio.file.Path)")
64+
@CallSite.Before(
65+
"java.lang.String java.nio.file.Files.readString(java.nio.file.Path, java.nio.charset.Charset)")
66+
@CallSite.Before(
67+
"java.io.BufferedReader java.nio.file.Files.newBufferedReader(java.nio.file.Path, java.nio.charset.Charset)")
68+
@CallSite.Before(
69+
"java.io.BufferedReader java.nio.file.Files.newBufferedReader(java.nio.file.Path)")
70+
@CallSite.Before(
71+
"java.util.stream.Stream java.nio.file.Files.lines(java.nio.file.Path, java.nio.charset.Charset)")
72+
@CallSite.Before("java.util.stream.Stream java.nio.file.Files.lines(java.nio.file.Path)")
73+
public static void beforeRead(@CallSite.Argument(0) @Nullable final Path path) {
74+
if (path != null) {
75+
FileIORaspHelper.INSTANCE.beforeFileLoaded(path.toString());
76+
}
77+
}
78+
}

dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/PathCallSite.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import datadog.trace.api.iast.Sink;
88
import datadog.trace.api.iast.VulnerabilityTypes;
99
import datadog.trace.api.iast.sink.PathTraversalModule;
10+
import java.nio.file.Path;
1011
import javax.annotation.Nullable;
1112

1213
@Sink(VulnerabilityTypes.PATH_TRAVERSAL)
@@ -24,6 +25,14 @@ public static void beforeResolve(@CallSite.Argument @Nullable final String other
2425
}
2526
}
2627

28+
@CallSite.Before("java.nio.file.Path java.nio.file.Path.resolve(java.nio.file.Path)")
29+
@CallSite.Before("java.nio.file.Path java.nio.file.Path.resolveSibling(java.nio.file.Path)")
30+
public static void beforeResolveWithPath(@CallSite.Argument @Nullable final Path other) {
31+
if (other != null) {
32+
raspCallback(other.toString());
33+
}
34+
}
35+
2736
private static void iastCallback(String other) {
2837
final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL;
2938
if (module != null) {

dd-java-agent/instrumentation/java/java-io-1.8/src/main/java/datadog/trace/instrumentation/java/lang/PathsCallSite.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,23 @@ public static void beforeGet(@CallSite.Argument @Nullable final URI uri) {
3535
}
3636
}
3737

38+
// Java 11+: Path.of — equivalent to Paths.get but defined on the Path interface
39+
@CallSite.Before("java.nio.file.Path java.nio.file.Path.of(java.lang.String, java.lang.String[])")
40+
public static void beforeOf(
41+
@CallSite.Argument @Nullable final String first,
42+
@CallSite.Argument @Nullable final String[] more) {
43+
if (first != null && more != null) {
44+
raspCallback(first, more);
45+
}
46+
}
47+
48+
@CallSite.Before("java.nio.file.Path java.nio.file.Path.of(java.net.URI)")
49+
public static void beforeOfUri(@CallSite.Argument @Nullable final URI uri) {
50+
if (uri != null) {
51+
raspCallback(uri);
52+
}
53+
}
54+
3855
private static void iastCallback(URI uri) {
3956
final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL;
4057
if (module != null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package datadog.trace.instrumentation.java.lang;
2+
3+
import datadog.trace.agent.tooling.csi.CallSite;
4+
import datadog.trace.api.appsec.RaspCallSites;
5+
import java.io.File;
6+
import javax.annotation.Nullable;
7+
8+
@CallSite(
9+
spi = {RaspCallSites.class},
10+
helpers = FileIORaspHelper.class)
11+
public class RandomAccessFileCallSite {
12+
13+
@CallSite.Before("void java.io.RandomAccessFile.<init>(java.lang.String, java.lang.String)")
14+
public static void beforeConstructor(
15+
@CallSite.Argument(0) @Nullable final String name,
16+
@CallSite.Argument(1) @Nullable final String mode) {
17+
if (name != null && mode != null) {
18+
FileIORaspHelper.INSTANCE.beforeRandomAccessFileOpened(name, mode);
19+
}
20+
}
21+
22+
@CallSite.Before("void java.io.RandomAccessFile.<init>(java.io.File, java.lang.String)")
23+
public static void beforeConstructorFile(
24+
@CallSite.Argument(0) @Nullable final File file,
25+
@CallSite.Argument(1) @Nullable final String mode) {
26+
if (file != null && mode != null) {
27+
FileIORaspHelper.INSTANCE.beforeRandomAccessFileOpened(file.getPath(), mode);
28+
}
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package datadog.trace.instrumentation.java.io
2+
3+
import datadog.trace.instrumentation.java.lang.FileIORaspHelper
4+
import foo.bar.TestFileChannelSuite
5+
6+
import java.nio.file.StandardOpenOption
7+
8+
class FileChannelCallSiteTest extends BaseIoRaspCallSiteTest {
9+
10+
void 'test RASP FileChannel.open read-only fires beforeFileLoaded'() {
11+
setup:
12+
final helper = Mock(FileIORaspHelper)
13+
FileIORaspHelper.INSTANCE = helper
14+
final path = newFile('test_rasp_fc_read.txt').toPath()
15+
16+
when:
17+
TestFileChannelSuite.openRead(path).close()
18+
19+
then:
20+
1 * helper.beforeFileLoaded(path.toString())
21+
1 * helper.beforeFileWritten(path.toString())
22+
}
23+
24+
void 'test RASP FileChannel.open write fires beforeFileWritten'() {
25+
setup:
26+
final helper = Mock(FileIORaspHelper)
27+
FileIORaspHelper.INSTANCE = helper
28+
final path = temporaryFolder.resolve('test_rasp_fc_write.txt')
29+
30+
when:
31+
TestFileChannelSuite.openWrite(path).close()
32+
33+
then:
34+
1 * helper.beforeFileLoaded(path.toString())
35+
1 * helper.beforeFileWritten(path.toString())
36+
}
37+
38+
void 'test RASP FileChannel.open with Set of options'() {
39+
setup:
40+
final helper = Mock(FileIORaspHelper)
41+
FileIORaspHelper.INSTANCE = helper
42+
final path = newFile('test_rasp_fc_set.txt').toPath()
43+
final options = EnumSet.of(StandardOpenOption.READ)
44+
45+
when:
46+
TestFileChannelSuite.openWithSet(path, options).close()
47+
48+
then:
49+
1 * helper.beforeFileLoaded(path.toString())
50+
1 * helper.beforeFileWritten(path.toString())
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package datadog.trace.instrumentation.java.io
2+
3+
import datadog.trace.instrumentation.java.lang.FileIORaspHelper
4+
import foo.bar.TestFileReaderSuite
5+
6+
class FileReaderCallSiteTest extends BaseIoRaspCallSiteTest {
7+
8+
void 'test RASP new file reader with path'() {
9+
setup:
10+
final helper = Mock(FileIORaspHelper)
11+
FileIORaspHelper.INSTANCE = helper
12+
final path = newFile('test_rasp_reader.txt').toString()
13+
14+
when:
15+
TestFileReaderSuite.newFileReader(path)
16+
17+
then:
18+
1 * helper.beforeFileLoaded(path)
19+
}
20+
21+
void 'test RASP new file reader with file'() {
22+
setup:
23+
final helper = Mock(FileIORaspHelper)
24+
FileIORaspHelper.INSTANCE = helper
25+
final file = newFile('test_rasp_reader_file.txt')
26+
27+
when:
28+
TestFileReaderSuite.newFileReader(file)
29+
30+
then:
31+
1 * helper.beforeFileLoaded(file.path)
32+
}
33+
}

0 commit comments

Comments
 (0)