Skip to content

Commit

Permalink
Create new ranges for vulns to prevent GC issues
Browse files Browse the repository at this point in the history
  • Loading branch information
manuel-alvarez-alvarez committed Jul 11, 2024
1 parent 95a52cc commit 0be95d1
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public Evidence(final String value) {

public Evidence(@Nonnull final String value, @Nullable final Range[] ranges) {
this.value = value;
this.ranges = ranges;
this.ranges = consolidate(ranges);
}

@Nonnull
Expand Down Expand Up @@ -62,6 +62,23 @@ public int hashCode() {
return result;
}

/**
* This method ensures that once a vulnerability has been found, all of it range names and values
* are strongly reachable preventing the GC from freeing them before the vul is reported. The
* newly created ranges have a lifespan equal to the target vulnerability.
*/
@Nullable
private static Range[] consolidate(@Nullable final Range[] ranges) {
if (ranges == null || ranges.length == 0) {
return ranges;
}
final Range[] result = new Range[ranges.length];
for (int i = 0; i < ranges.length; i++) {
result[i] = ranges[i].consolidate();
}
return result;
}

public static class Context {

private final Map<String, Object> context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,13 @@ public Range shift(final int offset) {
public boolean isMarked(final int mark) {
return (marks & mark) != NOT_MARKED;
}

/** Creates a version of the range without weak references to be used in vulnerabilities */
public Range consolidate() {
if (!source.isReference()) {
return this;
}
return new Range(
start, length, new Source(source.getOrigin(), source.getName(), source.getValue()), marks);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.datadog.iast.model.json.SourceTypeString;
import datadog.trace.api.iast.SourceTypes;
import datadog.trace.api.iast.Taintable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.lang.ref.Reference;
import java.util.Objects;
import java.util.StringJoiner;
Expand Down Expand Up @@ -51,14 +52,21 @@ public String getValue() {
return asString(value);
}

@SuppressFBWarnings(
value = "DM_STRING_CTOR",
justification = "New string instance requires constructor")
@SuppressWarnings("StringOperationCanBeSimplified")
@Nullable
private String asString(@Nullable final Object target) {
Object value = target;
if (value == PROPAGATION_PLACEHOLDER) {
value = null;
} else if (value instanceof Reference) {
value = ((Reference<?>) value).get();
if (value == null) {
if (value != null) {
// avoid exposing internal weak reference to the outer world
value = new String(value.toString());
} else {
value = GARBAGE_COLLECTED_REF;
if (!gcReported) {
gcReported = true;
Expand Down Expand Up @@ -96,10 +104,14 @@ public int hashCode() {
return Objects.hash(origin, getName(), getValue());
}

public Source attachValue(final CharSequence result) {
public Source attachValue(final Object newValue) {
if (value != PROPAGATION_PLACEHOLDER) {
return this;
}
return new Source(origin, name, result);
return new Source(origin, name, newValue);
}

public boolean isReference() {
return name instanceof Reference || value instanceof Reference;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ private static void internalTaint(
((Taintable) value).$$DD$setSource(source);
} else {
if (value instanceof CharSequence) {
source = source.attachValue((CharSequence) value);
source = attachSourceValue(source, (CharSequence) value);
to.taint(value, Ranges.forCharSequence((CharSequence) value, source, mark));
} else {
to.taint(value, Ranges.forObject(source, mark));
Expand All @@ -796,7 +796,7 @@ private static void internalTaint(
if (source == null) {
return;
}
source = source.attachValue(value);
source = attachSourceValue(source, value);
to.taint(value, Ranges.forCharSequence(value, source, mark));
}

Expand Down Expand Up @@ -865,6 +865,11 @@ private static Range[] markRanges(@Nonnull final Range[] ranges, final int mark)
return result;
}

private static Source attachSourceValue(final Source source, final CharSequence value) {
final Object newValue = sourceReference(value, value, true);
return newValue == null ? source : source.attachValue(newValue);
}

private static Range[] attachSourceValue(
@Nonnull final Range[] ranges, @Nonnull final CharSequence value) {
// unbound sources can only occur when there's a single range in the array
Expand All @@ -873,7 +878,7 @@ private static Range[] attachSourceValue(
}
final Range range = ranges[0];
final Source source = range.getSource();
final Source newSource = range.getSource().attachValue(value);
final Source newSource = attachSourceValue(source, value);
return newSource == source
? ranges
: Ranges.forCharSequence(value, newSource, range.getMarks());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package com.datadog.iast.model

import com.datadog.iast.model.json.VulnerabilityEncoding
import datadog.trace.api.iast.SourceTypes
import datadog.trace.test.util.DDSpecification
import groovy.json.JsonSlurper

import java.lang.ref.Reference
import java.lang.ref.WeakReference

import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED

class EvidenceTest extends DDSpecification {

Expand All @@ -27,4 +35,36 @@ class EvidenceTest extends DDSpecification {
then:
override
}

void 'test that evidences are consolidated preventing the GC from clearing data'() {
given:
final String name = "Hello world!"
final Reference<?> ref = new WeakReference<>(name)

when:
final source = new Source(SourceTypes.REQUEST_PARAMETER_NAME, ref, "a random value")
final batch = vulnBatchForSource(source)

then:
source.getName() == name
final parsedBefore = new JsonSlurper().parseText(VulnerabilityEncoding.toJson(batch))
parsedBefore["sources"][0]["name"] == name

when:
ref.clear()

then:
source.getName() == Source.GARBAGE_COLLECTED_REF
final parsedAfter = new JsonSlurper().parseText(VulnerabilityEncoding.toJson(batch))
parsedAfter["sources"][0]["name"] == name
}

private static VulnerabilityBatch vulnBatchForSource(final Source source) {
final location = Location.forClassAndMethodAndLine(EvidenceTest.name, 'vulnBatchForSource', 69)
final evidence = new Evidence("test", [new Range(0, 4, source, NOT_MARKED)] as Range[])
final vuln = new Vulnerability(VulnerabilityType.INSECURE_COOKIE, location, evidence)
final batch = new VulnerabilityBatch()
batch.add(vuln)
return batch
}
}

0 comments on commit 0be95d1

Please sign in to comment.