Skip to content

Commit bb9b598

Browse files
Add debug info to tainted objects to troubleshoot null ranges (#7455)
Add debug info to tainted objects to troubleshoot null ranges
1 parent 8eaed7d commit bb9b598

File tree

5 files changed

+131
-9
lines changed

5 files changed

+131
-9
lines changed

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedObject.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@
55
import com.datadog.iast.model.Range;
66
import datadog.trace.api.Config;
77
import java.lang.ref.WeakReference;
8+
import java.util.Arrays;
89
import javax.annotation.Nonnull;
910
import javax.annotation.Nullable;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
1013

1114
public class TaintedObject extends WeakReference<Object> {
1215

16+
private static final Logger LOGGER = LoggerFactory.getLogger(TaintedObject.class);
17+
1318
public static final int MAX_RANGE_COUNT = Config.get().getIastMaxRangeCount();
1419

1520
final int positiveHashCode;
@@ -21,6 +26,7 @@ public class TaintedObject extends WeakReference<Object> {
2126

2227
public TaintedObject(final @Nonnull Object obj, final @Nonnull Range[] ranges) {
2328
super(obj);
29+
validateRanges(ranges);
2430
this.positiveHashCode = System.identityHashCode(obj) & POSITIVE_MASK;
2531
// ensure ranges never go over the limit
2632
if (ranges.length > MAX_RANGE_COUNT) {
@@ -41,7 +47,12 @@ public Range[] getRanges() {
4147
}
4248

4349
public void setRanges(@Nonnull final Range[] ranges) {
44-
this.ranges = ranges;
50+
try {
51+
validateRanges(ranges);
52+
this.ranges = ranges;
53+
} catch (Throwable e) {
54+
LOGGER.debug("Error tainting object with custom ranges, ranges won't be updated", e);
55+
}
4556
}
4657

4758
@Override
@@ -57,4 +68,15 @@ public String toString() {
5768
+ (ranges == null ? 0 : ranges.length)
5869
+ " ranges)";
5970
}
71+
72+
private void validateRanges(final Range[] ranges) {
73+
if (ranges == null) {
74+
throw new IllegalArgumentException("ranges cannot be null");
75+
}
76+
for (Range range : ranges) {
77+
if (range == null) {
78+
throw new IllegalArgumentException("found null range in " + Arrays.toString(ranges));
79+
}
80+
}
81+
}
6082
}

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedObjects.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
@SuppressWarnings("UnusedReturnValue")
1919
public interface TaintedObjects extends Iterable<TaintedObject> {
2020

21+
Logger LOGGER = LoggerFactory.getLogger(TaintedObjects.class);
22+
2123
static TaintedObjects build(@Nonnull final TaintedMap map) {
2224
final TaintedObjectsImpl taintedObjects = new TaintedObjectsImpl(map);
2325
return IastSystem.DEBUG ? new TaintedObjectsDebugAdapter(taintedObjects) : taintedObjects;
@@ -41,12 +43,17 @@ private TaintedObjectsImpl(final @Nonnull TaintedMap map) {
4143
this.map = map;
4244
}
4345

44-
@Nonnull
46+
@Nullable
4547
@Override
4648
public TaintedObject taint(final @Nonnull Object obj, final @Nonnull Range[] ranges) {
47-
final TaintedObject tainted = new TaintedObject(obj, ranges);
48-
map.put(tainted);
49-
return tainted;
49+
try {
50+
final TaintedObject tainted = new TaintedObject(obj, ranges);
51+
map.put(tainted);
52+
return tainted;
53+
} catch (Throwable e) {
54+
LOGGER.debug("Error tainting object, it won't be tainted", e);
55+
return null;
56+
}
5057
}
5158

5259
@Nullable
@@ -73,7 +80,6 @@ public Iterator<TaintedObject> iterator() {
7380
}
7481

7582
final class TaintedObjectsDebugAdapter implements TaintedObjects, Wrapper<TaintedObjectsImpl> {
76-
static final Logger LOGGER = LoggerFactory.getLogger(TaintedObjects.class);
7783

7884
private final TaintedObjectsImpl delegated;
7985
private final UUID id;
@@ -125,10 +131,14 @@ public Iterator<TaintedObject> iterator() {
125131
return delegated.iterator();
126132
}
127133

128-
private void logTainted(final TaintedObject tainted) {
134+
private void logTainted(@Nullable final TaintedObject tainted) {
129135
if (LOGGER.isDebugEnabled()) {
130136
try {
131-
LOGGER.debug("taint {}: tainted={}", id, TaintedObjectEncoding.toJson(tainted));
137+
if (tainted == null) {
138+
LOGGER.debug("taint {}: ignored", id);
139+
} else {
140+
LOGGER.debug("taint {}: tainted={}", id, TaintedObjectEncoding.toJson(tainted));
141+
}
132142
} catch (final Throwable e) {
133143
LOGGER.error("Failed to debug new tainted object", e);
134144
}

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

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.datadog.iast.taint
22

3+
import ch.qos.logback.classic.Logger
4+
import ch.qos.logback.core.Appender
35
import com.datadog.iast.model.Range
46
import com.datadog.iast.model.Source
57
import datadog.trace.api.Config
@@ -14,7 +16,7 @@ class TaintedObjectTest extends Specification {
1416
given:
1517
final max = Config.get().iastMaxRangeCount
1618
final ranges = (0..max + 1)
17-
.collect { index -> new Range(index, 1, new Source(REQUEST_HEADER_NAME, 'a', 'b'), NOT_MARKED) }
19+
.collect { index -> new Range(index, 1, new Source(REQUEST_HEADER_NAME, 'a', 'b'), NOT_MARKED) }
1820

1921
when:
2022
final tainted = new TaintedObject('test', ranges.toArray(new Range[0]))
@@ -24,4 +26,36 @@ class TaintedObjectTest extends Specification {
2426
tainted.ranges.size() == max
2527
tainted.toString().contains("${tainted.ranges.size()} ranges")
2628
}
29+
30+
void 'test that objects are not tainted if null ranges are provided'() {
31+
setup:
32+
def toTaint = UUID.randomUUID().toString()
33+
def to = new TaintedObject(toTaint, Ranges.forCharSequence(toTaint, new Source(1 as byte, 'a', 'b'), NOT_MARKED))
34+
def logger = TaintedObject.LOGGER as Logger
35+
def appender = Mock(Appender<?>)
36+
logger.addAppender(appender)
37+
38+
when:
39+
to.setRanges(ranges as Range[])
40+
41+
then:
42+
noExceptionThrown()
43+
if (taint) {
44+
to.ranges.length == ranges.size()
45+
0 * appender.doAppend(_)
46+
} else {
47+
to.ranges.length == 1
48+
1 * appender.doAppend(_ as Object)
49+
}
50+
51+
cleanup:
52+
logger.detachAppender(appender)
53+
54+
where:
55+
ranges | taint
56+
null | false
57+
[] | true
58+
[new Range(0, 10, new Source(1 as byte, 'a', 'b'), 1), null] | false
59+
[new Range(0, 10, new Source(1 as byte, 'a', 'b'), 1), new Range(0, 10, new Source(2 as byte, 'a', 'b'), 1)] | true
60+
}
2761
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,19 @@ class TaintedObjectsLogTest extends DDSpecification {
7979
taintedObjects.size() == 1
8080
taintedObjects.iterator().size() == 1
8181
}
82+
83+
void 'should not taint null ranges'() {
84+
given:
85+
IastSystem.DEBUG = true
86+
logger.level = Level.ALL
87+
TaintedObjects taintedObjects = taintedObjects()
88+
final obj = 'A'
89+
90+
when:
91+
taintedObjects.taint(obj, null)
92+
93+
then:
94+
taintedObjects.empty
95+
}
8296
}
8397

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.datadog.iast.taint
2+
3+
import ch.qos.logback.classic.Logger
4+
import ch.qos.logback.core.Appender
5+
import com.datadog.iast.model.Range
6+
import com.datadog.iast.model.Source
7+
import spock.lang.Specification
8+
9+
class TaintedObjectsTest extends Specification {
10+
11+
void 'test that objects are not tainted if null ranges are provided'() {
12+
setup:
13+
def toTaint = UUID.randomUUID().toString()
14+
def to = TaintedObjects.build(TaintedMap.build(8))
15+
def logger = TaintedObjects.LOGGER as Logger
16+
def appender = Mock(Appender<?>)
17+
logger.addAppender(appender)
18+
19+
when:
20+
to.taint(toTaint, ranges as Range[])
21+
22+
then:
23+
noExceptionThrown()
24+
if (taint) {
25+
to.get(toTaint) != null
26+
0 * appender.doAppend(_)
27+
} else {
28+
to.get(toTaint) == null
29+
1 * appender.doAppend(_ as Object)
30+
}
31+
32+
cleanup:
33+
logger.detachAppender(appender)
34+
35+
where:
36+
ranges | taint
37+
null | false
38+
[] | true
39+
[new Range(0, 10, new Source(1 as byte, 'a', 'b'), 1), null] | false
40+
[new Range(0, 10, new Source(1 as byte, 'a', 'b'), 1), new Range(0, 10, new Source(2 as byte, 'a', 'b'), 1)] | true
41+
}
42+
}

0 commit comments

Comments
 (0)