Skip to content

Commit 70ac474

Browse files
authored
Fix jackson json parser propagation for field names (#7606)
1 parent b0e6c61 commit 70ac474

File tree

41 files changed

+1308
-66
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1308
-66
lines changed

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/iast/NamedContext.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public abstract class NamedContext {
1919

2020
public abstract void taintName(@Nullable String name);
2121

22+
public abstract void setCurrentName(@Nullable final String name);
23+
2224
@Nonnull
2325
public static <E> NamedContext getOrCreate(
2426
@Nonnull final ContextStore<E, NamedContext> store, @Nonnull final E target) {
@@ -47,6 +49,9 @@ public void taintValue(@Nullable final String value) {}
4749

4850
@Override
4951
public void taintName(@Nullable final String name) {}
52+
53+
@Override
54+
public void setCurrentName(@Nullable final String name) {}
5055
}
5156

5257
private static class NamedContextImpl extends NamedContext {
@@ -78,6 +83,11 @@ public void taintName(@Nullable final String name) {
7883
}
7984
}
8085

86+
@Override
87+
public void setCurrentName(@Nullable final String name) {
88+
currentName = name;
89+
}
90+
8191
private IastContext iastCtx() {
8292
if (!fetched) {
8393
fetched = true;

dd-java-agent/instrumentation/akka-http/akka-http-10.0/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ dependencies {
119119
iastTestImplementation(testFixtures(project(':dd-java-agent:agent-iast')))
120120
iastTestCompileOnly group: 'de.thetaphi', name: 'forbiddenapis', version: '3.4'
121121
iastTestRuntimeOnly project(':dd-java-agent:instrumentation:jackson-core')
122+
iastTestRuntimeOnly project(':dd-java-agent:instrumentation:jackson-core:jackson-core-2.8')
122123
iastTestRuntimeOnly project(':dd-java-agent:instrumentation:iast-instrumenter')
123124
iastTestRuntimeOnly project(':dd-java-agent:instrumentation:akka-http:akka-http-10.2-iast')
124125

@@ -161,6 +162,7 @@ dependencies {
161162
latestDepIastTestImplementation group: 'com.typesafe.akka', name: 'akka-actor_2.13', version: '2.8.+'
162163
latestDepIastTestImplementation group: 'com.typesafe.akka', name: 'akka-http-jackson_2.13', version: '[10.+,10.5.2)'
163164
latestDepIastTestImplementation(testFixtures(project(':dd-java-agent:agent-iast')))
165+
latestDepIastTestImplementation project(':dd-java-agent:instrumentation:jackson-core:jackson-core-2.12')
164166

165167
lagomTestImplementation libs.scala211
166168
lagomTestImplementation group: 'com.typesafe.akka', name: 'akka-http_2.11', version: '10.0.0'

dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@
125125
1 graphql.*
126126
1 ibm.security.*
127127
1 io.dropwizard.*
128+
2 io.ebean.*
129+
2 io.ebeaninternal.*
128130
1 io.github.lukehutch.fastclasspathscanner.*
129131
1 io.grpc.*
130132
1 io.leangen.geantyref.*

dd-java-agent/instrumentation/jackson-core/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,7 @@ dependencies {
2626

2727
testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
2828
testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)
29+
30+
latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.+'
31+
latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.+'
2932
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
muzzle {
2+
pass {
3+
group = 'com.fasterxml.jackson.core'
4+
module = 'jackson-core'
5+
versions = "[2.12.0, 2.16.0)"
6+
}
7+
}
8+
9+
apply from: "$rootDir/gradle/java.gradle"
10+
11+
addTestSuiteForDir('latestDepTest', 'test')
12+
13+
final jacksonVersion = '2.12.0'
14+
dependencies {
15+
compileOnly(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
16+
compileOnly(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)
17+
18+
testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
19+
testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)
20+
21+
latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.15.+'
22+
latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.15.+'
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.fasterxml.jackson.core.json;
2+
3+
import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer212Helper;
4+
5+
public final class JsonParser212Helper {
6+
private JsonParser212Helper() {}
7+
8+
public static boolean fetchIntern(UTF8StreamJsonParser jsonParser) {
9+
return ByteQuadsCanonicalizer212Helper.fetchIntern(jsonParser._symbols);
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.fasterxml.jackson.core.sym;
2+
3+
public final class ByteQuadsCanonicalizer212Helper {
4+
private ByteQuadsCanonicalizer212Helper() {}
5+
6+
public static boolean fetchIntern(ByteQuadsCanonicalizer symbols) {
7+
return symbols._intern;
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package datadog.trace.instrumentation.jackson_2_12.core;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers.hasClassNamed;
4+
import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.declaresMethod;
5+
import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.extendsClass;
6+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.*;
7+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
8+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf;
9+
import static java.util.Collections.singletonMap;
10+
import static net.bytebuddy.matcher.ElementMatchers.*;
11+
12+
import com.fasterxml.jackson.core.JsonParser;
13+
import com.fasterxml.jackson.core.JsonToken;
14+
import com.fasterxml.jackson.core.json.JsonParser212Helper;
15+
import com.fasterxml.jackson.core.json.UTF8StreamJsonParser;
16+
import com.google.auto.service.AutoService;
17+
import datadog.trace.agent.tooling.Instrumenter;
18+
import datadog.trace.agent.tooling.InstrumenterModule;
19+
import datadog.trace.api.iast.Propagation;
20+
import datadog.trace.bootstrap.ContextStore;
21+
import datadog.trace.bootstrap.InstrumentationContext;
22+
import datadog.trace.bootstrap.instrumentation.iast.NamedContext;
23+
import java.util.Map;
24+
import net.bytebuddy.asm.Advice;
25+
import net.bytebuddy.description.type.TypeDescription;
26+
import net.bytebuddy.matcher.ElementMatcher;
27+
28+
@AutoService(InstrumenterModule.class)
29+
public class JsonParserInstrumentation extends InstrumenterModule.Iast
30+
implements Instrumenter.ForTypeHierarchy {
31+
32+
static final String TARGET_TYPE = "com.fasterxml.jackson.core.JsonParser";
33+
static final ElementMatcher.Junction<ClassLoader> VERSION_POST_2_8_0_AND_PRE_2_12_0 =
34+
hasClassNamed("com.fasterxml.jackson.core.StreamReadCapability")
35+
.and(not(hasClassNamed("com.fasterxml.jackson.core.StreamWriteConstraints")));
36+
37+
public JsonParserInstrumentation() {
38+
super("jackson", "jackson-2_12");
39+
}
40+
41+
@Override
42+
public void methodAdvice(MethodTransformer transformer) {
43+
final String className = JsonParserInstrumentation.class.getName();
44+
transformer.applyAdvice(
45+
namedOneOf("getCurrentName", "nextFieldName")
46+
.and(isPublic())
47+
.and(takesNoArguments())
48+
.and(returns(String.class)),
49+
className + "$NameAdvice");
50+
}
51+
52+
@Override
53+
public String hierarchyMarkerType() {
54+
return TARGET_TYPE;
55+
}
56+
57+
@Override
58+
public ElementMatcher<TypeDescription> hierarchyMatcher() {
59+
return declaresMethod(namedOneOf("getCurrentName", "nextFieldName"))
60+
.and(
61+
extendsClass(named(hierarchyMarkerType()))
62+
.and(namedNoneOf("com.fasterxml.jackson.core.base.ParserMinimalBase")));
63+
}
64+
65+
@Override
66+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
67+
return VERSION_POST_2_8_0_AND_PRE_2_12_0;
68+
}
69+
70+
@Override
71+
public Map<String, String> contextStore() {
72+
return singletonMap(TARGET_TYPE, "datadog.trace.bootstrap.instrumentation.iast.NamedContext");
73+
}
74+
75+
@Override
76+
public String[] helperClassNames() {
77+
return new String[] {
78+
"com.fasterxml.jackson.core.json" + ".JsonParser212Helper",
79+
"com.fasterxml.jackson.core.sym" + ".ByteQuadsCanonicalizer212Helper",
80+
};
81+
}
82+
83+
public static class NameAdvice {
84+
85+
@Advice.OnMethodExit(suppress = Throwable.class)
86+
@Propagation
87+
public static void onExit(@Advice.This JsonParser jsonParser, @Advice.Return String result) {
88+
if (jsonParser != null
89+
&& result != null
90+
&& jsonParser.getCurrentToken() == JsonToken.FIELD_NAME) {
91+
final ContextStore<JsonParser, NamedContext> store =
92+
InstrumentationContext.get(JsonParser.class, NamedContext.class);
93+
final NamedContext context = NamedContext.getOrCreate(store, jsonParser);
94+
if (jsonParser instanceof UTF8StreamJsonParser
95+
&& JsonParser212Helper.fetchIntern((UTF8StreamJsonParser) jsonParser)) {
96+
context.setCurrentName(result);
97+
return;
98+
}
99+
context.taintName(result);
100+
}
101+
}
102+
}
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package datadog.trace.instrumentation.jackson212.core
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper
4+
import datadog.trace.agent.test.AgentTestRunner
5+
import datadog.trace.api.iast.InstrumentationBridge
6+
import datadog.trace.api.iast.SourceTypes
7+
import datadog.trace.api.iast.Taintable
8+
import datadog.trace.api.iast.propagation.PropagationModule
9+
import groovy.json.JsonOutput
10+
11+
import java.nio.charset.Charset
12+
13+
class JsonParserInstrumentationTest extends AgentTestRunner {
14+
15+
private final static String JSON_STRING = '{"root":"root_value","nested":["array_0","array_1"]}'
16+
17+
@Override
18+
protected void configurePreAgent() {
19+
injectSysConfig("dd.iast.enabled", "true")
20+
}
21+
22+
void 'test json parsing (tainted)'() {
23+
given:
24+
final source = new SourceImpl(origin: SourceTypes.REQUEST_BODY, name: 'body', value: JSON_STRING)
25+
final module = Mock(PropagationModule)
26+
InstrumentationBridge.registerIastModule(module)
27+
28+
and:
29+
final reader = new ObjectMapper().readerFor(Map)
30+
31+
when:
32+
final taintedResult = reader.readValue(target) as Map
33+
34+
then:
35+
JsonOutput.toJson(taintedResult) == JSON_STRING
36+
_ * module.taintObjectIfTainted(_, _)
37+
_ * module.findSource(_) >> source
38+
1 * module.taintString(_, 'root', source.origin, 'root', JSON_STRING)
39+
1 * module.taintString(_, 'nested', source.origin, 'nested', JSON_STRING)
40+
0 * _
41+
42+
where:
43+
target << [JSON_STRING]
44+
}
45+
46+
void 'test json parsing (tainted but field names)'() {
47+
given:
48+
final source = new SourceImpl(origin: SourceTypes.REQUEST_BODY, name: 'body', value: JSON_STRING)
49+
final module = Mock(PropagationModule)
50+
InstrumentationBridge.registerIastModule(module)
51+
52+
and:
53+
final reader = new ObjectMapper()
54+
55+
when:
56+
final taintedResult = reader.readValue(target, Map)
57+
58+
then:
59+
JsonOutput.toJson(taintedResult) == JSON_STRING
60+
_ * module.taintObjectIfTainted(_, _)
61+
_ * module.findSource(_) >> source
62+
0 * _
63+
64+
where:
65+
target << [new ByteArrayInputStream(JSON_STRING.getBytes(Charset.defaultCharset()))]
66+
}
67+
68+
void 'test json parsing (not tainted)'() {
69+
given:
70+
final module = Mock(PropagationModule)
71+
InstrumentationBridge.registerIastModule(module)
72+
73+
and:
74+
final reader = new ObjectMapper().readerFor(Map)
75+
76+
when:
77+
final taintedResult = reader.readValue(target) as Map
78+
79+
then:
80+
JsonOutput.toJson(taintedResult) == JSON_STRING
81+
_ * module.taintObjectIfTainted(_, _)
82+
_ * module.findSource(_) >> null
83+
0 * _
84+
85+
where:
86+
target << testSuite()
87+
}
88+
89+
private static List<Object> testSuite() {
90+
return [JSON_STRING, new ByteArrayInputStream(JSON_STRING.getBytes(Charset.defaultCharset()))]
91+
}
92+
93+
private static class SourceImpl implements Taintable.Source {
94+
byte origin
95+
String name
96+
String value
97+
}
98+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
muzzle {
2+
pass {
3+
group = 'com.fasterxml.jackson.core'
4+
module = 'jackson-core'
5+
versions = "[2.16.0,)"
6+
}
7+
}
8+
9+
apply from: "$rootDir/gradle/java.gradle"
10+
11+
addTestSuiteForDir('latestDepTest', 'test')
12+
13+
final jacksonVersion = '2.16.0'
14+
dependencies {
15+
compileOnly(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
16+
compileOnly(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)
17+
18+
testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
19+
testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)
20+
21+
latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.+'
22+
latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.+'
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.fasterxml.jackson.core.json;
2+
3+
import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer216Helper;
4+
5+
public final class JsonParser216Helper {
6+
private JsonParser216Helper() {}
7+
8+
public static boolean fetchInterner(UTF8StreamJsonParser jsonParser) {
9+
return ByteQuadsCanonicalizer216Helper.fetchInterner(jsonParser._symbols);
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.fasterxml.jackson.core.sym;
2+
3+
public final class ByteQuadsCanonicalizer216Helper {
4+
private ByteQuadsCanonicalizer216Helper() {}
5+
6+
public static boolean fetchInterner(ByteQuadsCanonicalizer symbols) {
7+
return symbols._interner != null;
8+
}
9+
}

0 commit comments

Comments
 (0)