Skip to content

Commit 39f09a5

Browse files
Fix string format when escaped patterns are used (#6795) (#6805)
(cherry picked from commit e8da14e) Co-authored-by: Manuel Álvarez Álvarez <manuel-alvarez-alvarez@users.noreply.github.com>
1 parent 12fc87d commit 39f09a5

File tree

2 files changed

+48
-10
lines changed

2 files changed

+48
-10
lines changed

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/StringModuleImpl.java

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static com.datadog.iast.taint.Ranges.mergeRanges;
66
import static com.datadog.iast.taint.Tainteds.canBeTainted;
77
import static com.datadog.iast.taint.Tainteds.getTainted;
8+
import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY;
89

910
import com.datadog.iast.model.Range;
1011
import com.datadog.iast.taint.Ranges;
@@ -20,8 +21,12 @@
2021
import java.util.Iterator;
2122
import java.util.LinkedList;
2223
import java.util.Locale;
24+
import java.util.Map;
25+
import java.util.function.Function;
2326
import java.util.regex.Matcher;
2427
import java.util.regex.Pattern;
28+
import java.util.stream.Collectors;
29+
import java.util.stream.Stream;
2530
import javax.annotation.Nonnull;
2631
import javax.annotation.Nullable;
2732

@@ -31,6 +36,10 @@ public class StringModuleImpl implements StringModule {
3136
private static final Pattern FORMAT_PATTERN =
3237
Pattern.compile("%(?<index>\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])");
3338

39+
/** Escaped format patterns * */
40+
private static final Map<String, String> ESCAPED_PATTERNS =
41+
Stream.of("%%", "%n").collect(Collectors.toMap(Function.identity(), String::format));
42+
3443
private static final Ranged END = Ranged.build(Integer.MAX_VALUE, 0);
3544

3645
private static final int NULL_STR_LENGTH = "null".length();
@@ -442,21 +451,32 @@ public void onStringFormat(
442451
final String placeholder = matcher.group();
443452
final Object parameter;
444453
final String formattedValue;
445-
final String index = matcher.group("index");
446-
if (index != null) {
447-
// indexes are 1-based
448-
final int parsedIndex = Integer.parseInt(index.substring(0, index.length() - 1)) - 1;
449-
// remove the index before the formatting without increment the current state
450-
parameter = parameters[parsedIndex];
451-
formattedValue = String.format(locale, placeholder.replace(index, ""), parameter);
454+
final TaintedObject taintedObject;
455+
final String escaped = ESCAPED_PATTERNS.get(placeholder);
456+
if (escaped != null) {
457+
parameter = placeholder;
458+
formattedValue = escaped;
459+
taintedObject = null;
452460
} else {
453-
parameter = parameters[paramIndex++];
454-
formattedValue = String.format(locale, placeholder, parameter);
461+
final String index = matcher.group("index");
462+
if (index != null) {
463+
// indexes are 1-based
464+
final int parsedIndex = Integer.parseInt(index.substring(0, index.length() - 1)) - 1;
465+
// remove the index before the formatting without increment the current state
466+
parameter = parameters[parsedIndex];
467+
formattedValue = String.format(locale, placeholder.replace(index, ""), parameter);
468+
} else {
469+
if (!checkParameterBounds(format, parameters, paramIndex)) {
470+
return; // return without tainting the string in case of error
471+
}
472+
parameter = parameters[paramIndex++];
473+
formattedValue = String.format(locale, placeholder, parameter);
474+
}
475+
taintedObject = to.get(parameter);
455476
}
456477
final Ranged placeholderPos = Ranged.build(matcher.start(), placeholder.length());
457478
final Range placeholderRange =
458479
addFormatTaintedRanges(placeholderPos, offset, formatRanges, finalRanges);
459-
final TaintedObject taintedObject = to.get(parameter);
460480
final Range[] paramRanges = taintedObject == null ? null : taintedObject.getRanges();
461481
final int shift = placeholderPos.getStart() + offset;
462482
addParameterTaintedRanges(
@@ -473,6 +493,20 @@ public void onStringFormat(
473493
}
474494
}
475495

496+
private static boolean checkParameterBounds(
497+
final String format, final Object[] parameters, int paramIndex) {
498+
if (paramIndex < parameters.length) {
499+
return true;
500+
}
501+
LOG.debug(
502+
SEND_TELEMETRY,
503+
"Error handling string format pattern {} with args {} at index {}",
504+
format,
505+
parameters.length,
506+
paramIndex);
507+
return false;
508+
}
509+
476510
@Override
477511
public void onStringFormat(
478512
@Nonnull final Iterable<String> literals,

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,10 @@ class StringModuleTest extends IastModuleImplTestBase {
915915
'Hello ==>%s<==' | ['World!'] | 'Hello ==>World!<==' // tainted placeholder [non tainted parameter]
916916
'He==>llo %s!<==' | ['World'] | 'He==>llo <====>World<====>!<==' // tainted placeholder (2) [non tainted parameter]
917917
'He==>llo %s!<==' | ['W==>or<==ld'] | 'He==>llo <==W==>or<==ld==>!<==' // tainted placeholder (3) [mixing with tainted parameter]
918+
'Hello %n %n %s!%n' | ['W==>or<==ld'] | 'Hello \n \n W==>or<==ld!\n' // \n character
919+
'Hello %% %% %s!%%' | ['W==>or<==ld'] | 'Hello % % W==>or<==ld!%' // % character
920+
'==>Hello %n %s!<==' | ['World'] | '==>Hello <====>\n<====> <====>World<====>!<==' // \n character in tainted format (each placeholder generates a separate range)
921+
'==>Hello %% %s!<==' | ['World'] | '==>Hello <====>%<====> <====>World<====>!<==' // % character in tainted format (each placeholder generates a separate range)
918922
}
919923
920924
void 'onStringFormat literals: #literals args: #argsTainted'() {

0 commit comments

Comments
 (0)