Skip to content

Commit

Permalink
Fix jackson json parser propagation for field names (#7606)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mariovido authored Sep 30, 2024
1 parent b0e6c61 commit 70ac474
Show file tree
Hide file tree
Showing 41 changed files with 1,308 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public abstract class NamedContext {

public abstract void taintName(@Nullable String name);

public abstract void setCurrentName(@Nullable final String name);

@Nonnull
public static <E> NamedContext getOrCreate(
@Nonnull final ContextStore<E, NamedContext> store, @Nonnull final E target) {
Expand Down Expand Up @@ -47,6 +49,9 @@ public void taintValue(@Nullable final String value) {}

@Override
public void taintName(@Nullable final String name) {}

@Override
public void setCurrentName(@Nullable final String name) {}
}

private static class NamedContextImpl extends NamedContext {
Expand Down Expand Up @@ -78,6 +83,11 @@ public void taintName(@Nullable final String name) {
}
}

@Override
public void setCurrentName(@Nullable final String name) {
currentName = name;
}

private IastContext iastCtx() {
if (!fetched) {
fetched = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ dependencies {
iastTestImplementation(testFixtures(project(':dd-java-agent:agent-iast')))
iastTestCompileOnly group: 'de.thetaphi', name: 'forbiddenapis', version: '3.4'
iastTestRuntimeOnly project(':dd-java-agent:instrumentation:jackson-core')
iastTestRuntimeOnly project(':dd-java-agent:instrumentation:jackson-core:jackson-core-2.8')
iastTestRuntimeOnly project(':dd-java-agent:instrumentation:iast-instrumenter')
iastTestRuntimeOnly project(':dd-java-agent:instrumentation:akka-http:akka-http-10.2-iast')

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

lagomTestImplementation libs.scala211
lagomTestImplementation group: 'com.typesafe.akka', name: 'akka-http_2.11', version: '10.0.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@
1 graphql.*
1 ibm.security.*
1 io.dropwizard.*
2 io.ebean.*
2 io.ebeaninternal.*
1 io.github.lukehutch.fastclasspathscanner.*
1 io.grpc.*
1 io.leangen.geantyref.*
Expand Down
3 changes: 3 additions & 0 deletions dd-java-agent/instrumentation/jackson-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@ dependencies {

testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)

latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.+'
latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.+'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
muzzle {
pass {
group = 'com.fasterxml.jackson.core'
module = 'jackson-core'
versions = "[2.12.0, 2.16.0)"
}
}

apply from: "$rootDir/gradle/java.gradle"

addTestSuiteForDir('latestDepTest', 'test')

final jacksonVersion = '2.12.0'
dependencies {
compileOnly(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
compileOnly(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)

testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)

latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.15.+'
latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.15.+'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.fasterxml.jackson.core.json;

import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer212Helper;

public final class JsonParser212Helper {
private JsonParser212Helper() {}

public static boolean fetchIntern(UTF8StreamJsonParser jsonParser) {
return ByteQuadsCanonicalizer212Helper.fetchIntern(jsonParser._symbols);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.fasterxml.jackson.core.sym;

public final class ByteQuadsCanonicalizer212Helper {
private ByteQuadsCanonicalizer212Helper() {}

public static boolean fetchIntern(ByteQuadsCanonicalizer symbols) {
return symbols._intern;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package datadog.trace.instrumentation.jackson_2_12.core;

import static datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers.hasClassNamed;
import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.declaresMethod;
import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.extendsClass;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.*;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.*;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.json.JsonParser212Helper;
import com.fasterxml.jackson.core.json.UTF8StreamJsonParser;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.iast.Propagation;
import datadog.trace.bootstrap.ContextStore;
import datadog.trace.bootstrap.InstrumentationContext;
import datadog.trace.bootstrap.instrumentation.iast.NamedContext;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumenterModule.class)
public class JsonParserInstrumentation extends InstrumenterModule.Iast
implements Instrumenter.ForTypeHierarchy {

static final String TARGET_TYPE = "com.fasterxml.jackson.core.JsonParser";
static final ElementMatcher.Junction<ClassLoader> VERSION_POST_2_8_0_AND_PRE_2_12_0 =
hasClassNamed("com.fasterxml.jackson.core.StreamReadCapability")
.and(not(hasClassNamed("com.fasterxml.jackson.core.StreamWriteConstraints")));

public JsonParserInstrumentation() {
super("jackson", "jackson-2_12");
}

@Override
public void methodAdvice(MethodTransformer transformer) {
final String className = JsonParserInstrumentation.class.getName();
transformer.applyAdvice(
namedOneOf("getCurrentName", "nextFieldName")
.and(isPublic())
.and(takesNoArguments())
.and(returns(String.class)),
className + "$NameAdvice");
}

@Override
public String hierarchyMarkerType() {
return TARGET_TYPE;
}

@Override
public ElementMatcher<TypeDescription> hierarchyMatcher() {
return declaresMethod(namedOneOf("getCurrentName", "nextFieldName"))
.and(
extendsClass(named(hierarchyMarkerType()))
.and(namedNoneOf("com.fasterxml.jackson.core.base.ParserMinimalBase")));
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
return VERSION_POST_2_8_0_AND_PRE_2_12_0;
}

@Override
public Map<String, String> contextStore() {
return singletonMap(TARGET_TYPE, "datadog.trace.bootstrap.instrumentation.iast.NamedContext");
}

@Override
public String[] helperClassNames() {
return new String[] {
"com.fasterxml.jackson.core.json" + ".JsonParser212Helper",
"com.fasterxml.jackson.core.sym" + ".ByteQuadsCanonicalizer212Helper",
};
}

public static class NameAdvice {

@Advice.OnMethodExit(suppress = Throwable.class)
@Propagation
public static void onExit(@Advice.This JsonParser jsonParser, @Advice.Return String result) {
if (jsonParser != null
&& result != null
&& jsonParser.getCurrentToken() == JsonToken.FIELD_NAME) {
final ContextStore<JsonParser, NamedContext> store =
InstrumentationContext.get(JsonParser.class, NamedContext.class);
final NamedContext context = NamedContext.getOrCreate(store, jsonParser);
if (jsonParser instanceof UTF8StreamJsonParser
&& JsonParser212Helper.fetchIntern((UTF8StreamJsonParser) jsonParser)) {
context.setCurrentName(result);
return;
}
context.taintName(result);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package datadog.trace.instrumentation.jackson212.core

import com.fasterxml.jackson.databind.ObjectMapper
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.api.iast.InstrumentationBridge
import datadog.trace.api.iast.SourceTypes
import datadog.trace.api.iast.Taintable
import datadog.trace.api.iast.propagation.PropagationModule
import groovy.json.JsonOutput

import java.nio.charset.Charset

class JsonParserInstrumentationTest extends AgentTestRunner {

private final static String JSON_STRING = '{"root":"root_value","nested":["array_0","array_1"]}'

@Override
protected void configurePreAgent() {
injectSysConfig("dd.iast.enabled", "true")
}

void 'test json parsing (tainted)'() {
given:
final source = new SourceImpl(origin: SourceTypes.REQUEST_BODY, name: 'body', value: JSON_STRING)
final module = Mock(PropagationModule)
InstrumentationBridge.registerIastModule(module)
and:
final reader = new ObjectMapper().readerFor(Map)
when:
final taintedResult = reader.readValue(target) as Map
then:
JsonOutput.toJson(taintedResult) == JSON_STRING
_ * module.taintObjectIfTainted(_, _)
_ * module.findSource(_) >> source
1 * module.taintString(_, 'root', source.origin, 'root', JSON_STRING)
1 * module.taintString(_, 'nested', source.origin, 'nested', JSON_STRING)
0 * _
where:
target << [JSON_STRING]
}
void 'test json parsing (tainted but field names)'() {
given:
final source = new SourceImpl(origin: SourceTypes.REQUEST_BODY, name: 'body', value: JSON_STRING)
final module = Mock(PropagationModule)
InstrumentationBridge.registerIastModule(module)
and:
final reader = new ObjectMapper()
when:
final taintedResult = reader.readValue(target, Map)
then:
JsonOutput.toJson(taintedResult) == JSON_STRING
_ * module.taintObjectIfTainted(_, _)
_ * module.findSource(_) >> source
0 * _
where:
target << [new ByteArrayInputStream(JSON_STRING.getBytes(Charset.defaultCharset()))]
}
void 'test json parsing (not tainted)'() {
given:
final module = Mock(PropagationModule)
InstrumentationBridge.registerIastModule(module)
and:
final reader = new ObjectMapper().readerFor(Map)
when:
final taintedResult = reader.readValue(target) as Map
then:
JsonOutput.toJson(taintedResult) == JSON_STRING
_ * module.taintObjectIfTainted(_, _)
_ * module.findSource(_) >> null
0 * _
where:
target << testSuite()
}
private static List<Object> testSuite() {
return [JSON_STRING, new ByteArrayInputStream(JSON_STRING.getBytes(Charset.defaultCharset()))]
}
private static class SourceImpl implements Taintable.Source {
byte origin
String name
String value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
muzzle {
pass {
group = 'com.fasterxml.jackson.core'
module = 'jackson-core'
versions = "[2.16.0,)"
}
}

apply from: "$rootDir/gradle/java.gradle"

addTestSuiteForDir('latestDepTest', 'test')

final jacksonVersion = '2.16.0'
dependencies {
compileOnly(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
compileOnly(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)

testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)

latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.+'
latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.+'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.fasterxml.jackson.core.json;

import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer216Helper;

public final class JsonParser216Helper {
private JsonParser216Helper() {}

public static boolean fetchInterner(UTF8StreamJsonParser jsonParser) {
return ByteQuadsCanonicalizer216Helper.fetchInterner(jsonParser._symbols);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.fasterxml.jackson.core.sym;

public final class ByteQuadsCanonicalizer216Helper {
private ByteQuadsCanonicalizer216Helper() {}

public static boolean fetchInterner(ByteQuadsCanonicalizer symbols) {
return symbols._interner != null;
}
}
Loading

0 comments on commit 70ac474

Please sign in to comment.