Skip to content

Commit 4712a4b

Browse files
Shortcircuit TBV visitor early for types we don't want to visit
1 parent 21c0b2d commit 4712a4b

File tree

6 files changed

+104
-11
lines changed

6 files changed

+104
-11
lines changed

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/GrpcRequestMessageHandler.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ public Flow<Void> apply(final RequestContext ctx, final Object o) {
4646
}
4747

4848
static boolean visitProtobufArtifact(@Nonnull final Class<?> kls) {
49+
if (CharSequence.class.isAssignableFrom(kls)) {
50+
return true; // we want to visit all strings in the message
51+
}
4952
if (kls.getSuperclass().getName().startsWith(GENERATED_MESSAGE)) {
5053
return true; // GRPC custom messages
5154
}

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/TrustBoundaryViolationModuleImpl.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,16 @@ public void onSessionValue(@Nonnull String name, Object value) {
4747
* types.
4848
*/
4949
private static boolean visitClass(final Class<?> clazz) {
50+
if (CharSequence.class.isAssignableFrom(clazz)) {
51+
return true; // we want to visit all strings added to the session
52+
}
53+
if (clazz.isArray()) {
54+
return true; // we also want to visit arrays
55+
}
5056
if ((Iterable.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz))) {
5157
final String className = clazz.getName();
52-
return ALLOWED_COLLECTION_PKGS.apply(className) > 0;
58+
return ALLOWED_COLLECTION_PKGS.apply(className)
59+
> 0; // ignore unknown collection types (e.g. lazy ones)
5360
}
5461
return false;
5562
}

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/ObjectVisitor.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ private State visit(final int depth, final String path, final Object value) {
7171
if (depth > maxDepth) {
7272
return CONTINUE;
7373
}
74-
if (!visited.add(value)) {
74+
if (!classFilter.test(value.getClass()) || !visited.add(value)) {
7575
return CONTINUE;
7676
}
7777
State state = CONTINUE;
@@ -107,9 +107,6 @@ private State visitArray(final int depth, final String path, final Object[] arra
107107
}
108108

109109
private State visitMap(final int depth, final String path, final Map<?, ?> map) {
110-
if (!classFilter.test(map.getClass())) {
111-
return CONTINUE;
112-
}
113110
final int mapDepth = depth + 1;
114111
for (final Map.Entry<?, ?> entry : map.entrySet()) {
115112
final Object key = entry.getKey();
@@ -133,9 +130,6 @@ private State visitMap(final int depth, final String path, final Map<?, ?> map)
133130
}
134131

135132
private State visitIterable(final int depth, final String path, final Iterable<?> iterable) {
136-
if (!classFilter.test(iterable.getClass())) {
137-
return CONTINUE;
138-
}
139133
final int iterableDepth = depth + 1;
140134
int index = 0;
141135
for (final Object item : iterable) {
@@ -153,7 +147,7 @@ private State visitIterable(final int depth, final String path, final Iterable<?
153147
private State visitObject(final int depth, final String path, final Object value) {
154148
final int childDepth = depth + 1;
155149
State state = visitor.visit(path, value);
156-
if (state != State.CONTINUE || !classFilter.test(value.getClass())) {
150+
if (state != State.CONTINUE) {
157151
return state;
158152
}
159153
Class<?> klass = value.getClass();

dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/GrpcRequestMessageHandlerTest.groovy

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,7 @@ class GrpcRequestMessageHandlerTest extends IastModuleImplTestBase {
7575
when: 'the message is not a protobuf instance'
7676
ObjectVisitor.visit(nonProtobufMessage, visitor, filter)
7777

78-
then: 'only the root object is visited'
79-
1 * visitor.visit('root', nonProtobufMessage) >> CONTINUE
78+
then: 'nothing is visited'
8079
0 * visitor._
8180

8281
when: 'the message is a protobuf message'

dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/TrustBoundaryViolationModuleTest.groovy

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,22 @@ import com.datadog.iast.model.VulnerabilityType
88
import com.datadog.iast.taint.Ranges
99
import datadog.trace.api.iast.InstrumentationBridge
1010
import datadog.trace.api.iast.SourceTypes
11+
import foo.bar.Pojo
1112
import foo.bar.VisitableClass
13+
import net.bytebuddy.ByteBuddy
14+
import net.bytebuddy.implementation.MethodDelegation
15+
import net.bytebuddy.implementation.bind.annotation.AllArguments
16+
import net.bytebuddy.implementation.bind.annotation.RuntimeType
17+
import net.bytebuddy.implementation.bind.annotation.SuperMethod
18+
import net.bytebuddy.implementation.bind.annotation.This
19+
20+
import java.lang.reflect.Method
21+
import java.util.concurrent.atomic.AtomicInteger
1222

1323
import static java.util.Arrays.asList
24+
import static net.bytebuddy.matcher.ElementMatchers.isConstructor
25+
import static net.bytebuddy.matcher.ElementMatchers.named
26+
import static net.bytebuddy.matcher.ElementMatchers.not
1427

1528
class TrustBoundaryViolationModuleTest extends IastModuleImplTestBase {
1629

@@ -146,6 +159,32 @@ class TrustBoundaryViolationModuleTest extends IastModuleImplTestBase {
146159
0 * reporter.report(_, _ as Vulnerability)
147160
}
148161

162+
void 'test that proxy objects are not visited in TBV'() {
163+
setup:
164+
final pojo = new ByteBuddy()
165+
.subclass(Pojo)
166+
.method(not(isConstructor().or(named('toString'))))
167+
.intercept(MethodDelegation.to(PojoInterceptor))
168+
.make()
169+
.load(Pojo.classLoader)
170+
.loaded
171+
.newInstance()
172+
173+
when:
174+
module.onSessionValue('test', pojo)
175+
176+
then:
177+
PojoInterceptor.INVOCATIONS.get() == 0
178+
179+
when:
180+
pojo.setId(23)
181+
pojo.setName('test')
182+
pojo.equals(new Pojo(id: 12, name: 'another'))
183+
184+
then:
185+
PojoInterceptor.INVOCATIONS.get() == 3
186+
}
187+
149188
private static void assertVulnerability(final Vulnerability vuln, String expectedValue) {
150189
assert vuln != null
151190
assert vuln.getType() == VulnerabilityType.TRUST_BOUNDARY_VIOLATION
@@ -170,3 +209,16 @@ class DynamicList<E> {
170209
throw new UnsupportedOperationException('Do not touch me!')
171210
}
172211
}
212+
213+
class PojoInterceptor {
214+
215+
static final AtomicInteger INVOCATIONS = new AtomicInteger(0)
216+
217+
@RuntimeType
218+
static Object intercept(@This Object self,
219+
@AllArguments Object[] args,
220+
@SuperMethod Method superMethod) throws Throwable {
221+
INVOCATIONS.addAndGet(1)
222+
return superMethod.invoke(self, args)
223+
}
224+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package foo.bar;
2+
3+
import java.util.Objects;
4+
5+
public class Pojo {
6+
7+
private int id;
8+
private String name;
9+
10+
public int getId() {
11+
return id;
12+
}
13+
14+
public void setId(int id) {
15+
this.id = id;
16+
}
17+
18+
public String getName() {
19+
return name;
20+
}
21+
22+
public void setName(String name) {
23+
this.name = name;
24+
}
25+
26+
@Override
27+
public boolean equals(Object o) {
28+
if (this == o) return true;
29+
if (o == null || getClass() != o.getClass()) return false;
30+
Pojo pojo = (Pojo) o;
31+
return id == pojo.id;
32+
}
33+
34+
@Override
35+
public int hashCode() {
36+
return Objects.hashCode(id);
37+
}
38+
}

0 commit comments

Comments
 (0)