Skip to content

Commit 2076c7b

Browse files
committed
fix(appsec): extract readContent to helper class to fix muzzle failure
The static readContent method in ParseRequestAdvice created a self-reference in the inlined advice bytecode (invokestatic on CommonsFileUploadAppSecModule$ParseRequestAdvice) that muzzle could not resolve in the application classloader, causing the instrumentation to be silently skipped. Moves readContent to a new FileItemContentReader helper class declared via helperClassNames(), which muzzle skips and the HelperInjector injects into the application classloader at runtime.
1 parent 22d70a0 commit 2076c7b

File tree

3 files changed

+49
-34
lines changed

3 files changed

+49
-34
lines changed

dd-java-agent/instrumentation/commons-fileupload-1.5/src/main/java/datadog/trace/instrumentation/commons/fileupload/CommonsFileUploadAppSecModule.java

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
import datadog.trace.api.gateway.RequestContext;
1818
import datadog.trace.api.gateway.RequestContextSlot;
1919
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
20-
import java.io.IOException;
21-
import java.io.InputStream;
22-
import java.nio.charset.StandardCharsets;
2320
import java.util.ArrayList;
2421
import java.util.List;
2522
import java.util.function.BiFunction;
@@ -39,6 +36,13 @@ public String instrumentedType() {
3936
return "org.apache.commons.fileupload.servlet.ServletFileUpload";
4037
}
4138

39+
@Override
40+
public String[] helperClassNames() {
41+
return new String[] {
42+
"datadog.trace.instrumentation.commons.fileupload.FileItemContentReader",
43+
};
44+
}
45+
4246
@Override
4347
public void methodAdvice(MethodTransformer transformer) {
4448
transformer.applyAdvice(
@@ -51,8 +55,6 @@ public void methodAdvice(MethodTransformer transformer) {
5155
@RequiresRequestContext(RequestContextSlot.APPSEC)
5256
public static class ParseRequestAdvice {
5357

54-
static final int MAX_FILE_CONTENT_BYTES = 4096;
55-
5658
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
5759
static void after(
5860
@Advice.Return final List<FileItem> fileItems,
@@ -114,7 +116,7 @@ static void after(
114116
if (name == null || name.isEmpty()) {
115117
continue;
116118
}
117-
filesContent.add(readContent(fileItem));
119+
filesContent.add(FileItemContentReader.readContent(fileItem));
118120
}
119121
if (filesContent.isEmpty()) {
120122
return;
@@ -131,20 +133,5 @@ static void after(
131133
}
132134
}
133135
}
134-
135-
static String readContent(FileItem fileItem) {
136-
try (InputStream is = fileItem.getInputStream()) {
137-
byte[] buf = new byte[MAX_FILE_CONTENT_BYTES];
138-
int total = 0;
139-
int n;
140-
while (total < MAX_FILE_CONTENT_BYTES
141-
&& (n = is.read(buf, total, MAX_FILE_CONTENT_BYTES - total)) != -1) {
142-
total += n;
143-
}
144-
return new String(buf, 0, total, StandardCharsets.ISO_8859_1);
145-
} catch (IOException ignored) {
146-
return "";
147-
}
148-
}
149136
}
150137
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package datadog.trace.instrumentation.commons.fileupload;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.nio.charset.StandardCharsets;
6+
import org.apache.commons.fileupload.FileItem;
7+
8+
/** Helper class injected into the application classloader by the AppSec instrumentation. */
9+
public final class FileItemContentReader {
10+
public static final int MAX_CONTENT_BYTES = 4096;
11+
12+
public static String readContent(FileItem fileItem) {
13+
try (InputStream is = fileItem.getInputStream()) {
14+
byte[] buf = new byte[MAX_CONTENT_BYTES];
15+
int total = 0;
16+
int n;
17+
while (total < MAX_CONTENT_BYTES
18+
&& (n = is.read(buf, total, MAX_CONTENT_BYTES - total)) != -1) {
19+
total += n;
20+
}
21+
return new String(buf, 0, total, StandardCharsets.ISO_8859_1);
22+
} catch (IOException ignored) {
23+
return "";
24+
}
25+
}
26+
27+
private FileItemContentReader() {}
28+
}

dd-java-agent/instrumentation/commons-fileupload-1.5/src/test/groovy/CommonsFileUploadAppSecModuleTest.groovy

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import datadog.trace.instrumentation.commons.fileupload.CommonsFileUploadAppSecModule
1+
import datadog.trace.instrumentation.commons.fileupload.FileItemContentReader
22
import org.apache.commons.fileupload.FileItem
33
import spock.lang.Specification
44

@@ -10,20 +10,20 @@ class CommonsFileUploadAppSecModuleTest extends Specification {
1010
def item = fileItem(content)
1111

1212
expect:
13-
CommonsFileUploadAppSecModule.ParseRequestAdvice.readContent(item) == content
13+
FileItemContentReader.readContent(item) == content
1414
}
1515

16-
def "readContent truncates content to MAX_FILE_CONTENT_BYTES"() {
16+
def "readContent truncates content to MAX_CONTENT_BYTES"() {
1717
given:
18-
def largeContent = 'X' * (CommonsFileUploadAppSecModule.ParseRequestAdvice.MAX_FILE_CONTENT_BYTES + 500)
18+
def largeContent = 'X' * (FileItemContentReader.MAX_CONTENT_BYTES + 500)
1919
def item = fileItem(largeContent)
2020

2121
when:
22-
def result = CommonsFileUploadAppSecModule.ParseRequestAdvice.readContent(item)
22+
def result = FileItemContentReader.readContent(item)
2323

2424
then:
25-
result.length() == CommonsFileUploadAppSecModule.ParseRequestAdvice.MAX_FILE_CONTENT_BYTES
26-
result == 'X' * CommonsFileUploadAppSecModule.ParseRequestAdvice.MAX_FILE_CONTENT_BYTES
25+
result.length() == FileItemContentReader.MAX_CONTENT_BYTES
26+
result == 'X' * FileItemContentReader.MAX_CONTENT_BYTES
2727
}
2828

2929
def "readContent returns empty string when getInputStream throws"() {
@@ -32,27 +32,27 @@ class CommonsFileUploadAppSecModuleTest extends Specification {
3232
item.getInputStream() >> { throw new IOException('simulated error') }
3333

3434
expect:
35-
CommonsFileUploadAppSecModule.ParseRequestAdvice.readContent(item) == ''
35+
FileItemContentReader.readContent(item) == ''
3636
}
3737

3838
def "readContent returns empty string for empty content"() {
3939
given:
4040
def item = fileItem('')
4141

4242
expect:
43-
CommonsFileUploadAppSecModule.ParseRequestAdvice.readContent(item) == ''
43+
FileItemContentReader.readContent(item) == ''
4444
}
4545

46-
def "readContent reads exactly MAX_FILE_CONTENT_BYTES when content equals the limit"() {
46+
def "readContent reads exactly MAX_CONTENT_BYTES when content equals the limit"() {
4747
given:
48-
def content = 'A' * CommonsFileUploadAppSecModule.ParseRequestAdvice.MAX_FILE_CONTENT_BYTES
48+
def content = 'A' * FileItemContentReader.MAX_CONTENT_BYTES
4949
def item = fileItem(content)
5050

5151
when:
52-
def result = CommonsFileUploadAppSecModule.ParseRequestAdvice.readContent(item)
52+
def result = FileItemContentReader.readContent(item)
5353

5454
then:
55-
result.length() == CommonsFileUploadAppSecModule.ParseRequestAdvice.MAX_FILE_CONTENT_BYTES
55+
result.length() == FileItemContentReader.MAX_CONTENT_BYTES
5656
result == content
5757
}
5858

0 commit comments

Comments
 (0)