Skip to content

Commit ea7c8d4

Browse files
[ML] Prevent stack overflow while copying ML jobs and datafeeds (#36370)
ML jobs and datafeeds wrap collections into their unmodifiable equivalents in their constructor. However, the copying builder does not make a copy of some of those collections resulting in wrapping those again and again. This can eventually result to stack overflow. This commit addressed this issue by copying the collections in question in the copying builder constructor. Closes #36360
1 parent 12d15dd commit ea7c8d4

File tree

6 files changed

+29
-12
lines changed

6 files changed

+29
-12
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/ml/datafeed/DatafeedConfig.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,11 @@ public Builder(DatafeedConfig config) {
286286
this.jobId = config.jobId;
287287
this.queryDelay = config.queryDelay;
288288
this.frequency = config.frequency;
289-
this.indices = config.indices;
290-
this.types = config.types;
289+
this.indices = config.indices == null ? null : new ArrayList<>(config.indices);
290+
this.types = config.types == null ? null : new ArrayList<>(config.types);
291291
this.query = config.query;
292292
this.aggregations = config.aggregations;
293-
this.scriptFields = config.scriptFields;
293+
this.scriptFields = config.scriptFields == null ? null : new ArrayList<>(config.scriptFields);
294294
this.scrollSize = config.scrollSize;
295295
this.chunkingConfig = config.chunkingConfig;
296296
}

client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/config/Job.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828
import org.elasticsearch.common.xcontent.XContentBuilder;
2929

3030
import java.io.IOException;
31+
import java.util.ArrayList;
3132
import java.util.Collections;
3233
import java.util.Date;
34+
import java.util.HashMap;
3335
import java.util.List;
3436
import java.util.Map;
3537
import java.util.Objects;
@@ -449,7 +451,7 @@ public Builder(String id) {
449451
public Builder(Job job) {
450452
this.id = job.getId();
451453
this.jobType = job.getJobType();
452-
this.groups = job.getGroups();
454+
this.groups = new ArrayList<>(job.getGroups());
453455
this.description = job.getDescription();
454456
this.analysisConfig = job.getAnalysisConfig();
455457
this.analysisLimits = job.getAnalysisLimits();
@@ -463,7 +465,7 @@ public Builder(Job job) {
463465
this.backgroundPersistInterval = job.getBackgroundPersistInterval();
464466
this.modelSnapshotRetentionDays = job.getModelSnapshotRetentionDays();
465467
this.resultsRetentionDays = job.getResultsRetentionDays();
466-
this.customSettings = job.getCustomSettings();
468+
this.customSettings = job.getCustomSettings() == null ? null : new HashMap<>(job.getCustomSettings());
467469
this.modelSnapshotId = job.getModelSnapshotId();
468470
this.resultsIndexName = job.getResultsIndexNameNoPrefix();
469471
this.deleting = job.getDeleting();

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfig.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.Collection;
3939
import java.util.Collections;
4040
import java.util.Comparator;
41+
import java.util.HashMap;
4142
import java.util.List;
4243
import java.util.Map;
4344
import java.util.Objects;
@@ -461,14 +462,14 @@ public Builder(DatafeedConfig config) {
461462
this.jobId = config.jobId;
462463
this.queryDelay = config.queryDelay;
463464
this.frequency = config.frequency;
464-
this.indices = config.indices;
465-
this.types = config.types;
465+
this.indices = new ArrayList<>(config.indices);
466+
this.types = new ArrayList<>(config.types);
466467
this.query = config.query;
467468
this.aggregations = config.aggregations;
468-
this.scriptFields = config.scriptFields;
469+
this.scriptFields = config.scriptFields == null ? null : new ArrayList<>(config.scriptFields);
469470
this.scrollSize = config.scrollSize;
470471
this.chunkingConfig = config.chunkingConfig;
471-
this.headers = config.headers;
472+
this.headers = new HashMap<>(config.headers);
472473
}
473474

474475
public void setId(String datafeedId) {

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/Job.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.Collection;
3333
import java.util.Collections;
3434
import java.util.Date;
35+
import java.util.HashMap;
3536
import java.util.HashSet;
3637
import java.util.List;
3738
import java.util.Map;
@@ -664,7 +665,7 @@ public Builder(Job job) {
664665
this.id = job.getId();
665666
this.jobType = job.getJobType();
666667
this.jobVersion = job.getJobVersion();
667-
this.groups = job.getGroups();
668+
this.groups = new ArrayList<>(job.getGroups());
668669
this.description = job.getDescription();
669670
this.analysisConfig = job.getAnalysisConfig();
670671
this.analysisLimits = job.getAnalysisLimits();
@@ -678,7 +679,7 @@ public Builder(Job job) {
678679
this.backgroundPersistInterval = job.getBackgroundPersistInterval();
679680
this.modelSnapshotRetentionDays = job.getModelSnapshotRetentionDays();
680681
this.resultsRetentionDays = job.getResultsRetentionDays();
681-
this.customSettings = job.getCustomSettings();
682+
this.customSettings = job.getCustomSettings() == null ? null : new HashMap<>(job.getCustomSettings());
682683
this.modelSnapshotId = job.getModelSnapshotId();
683684
this.resultsIndexName = job.getResultsIndexNameNoPrefix();
684685
this.deleting = job.isDeleting();

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfigTests.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
package org.elasticsearch.xpack.core.ml.datafeed;
77

88
import com.carrotsearch.randomizedtesting.generators.CodepointSetGenerator;
9-
109
import org.elasticsearch.ElasticsearchException;
1110
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
1211
import org.elasticsearch.common.io.stream.Writeable;
@@ -490,6 +489,13 @@ public void testDefaultFrequency_GivenAggregationsWithHistogramInterval_1_Hour()
490489
assertEquals(TimeValue.timeValueHours(1), datafeed.defaultFrequency(TimeValue.timeValueHours(12)));
491490
}
492491

492+
public void testCopyingDatafeedDoesNotCauseStackOverflow() {
493+
DatafeedConfig datafeed = createTestInstance();
494+
for (int i = 0; i < 100000; i++) {
495+
datafeed = new DatafeedConfig.Builder(datafeed).build();
496+
}
497+
}
498+
493499
public static String randomValidDatafeedId() {
494500
CodepointSetGenerator generator = new CodepointSetGenerator("abcdefghijklmnopqrstuvwxyz".toCharArray());
495501
return generator.ofCodePointsLength(random(), 10, 10);

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/JobTests.java

+7
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,13 @@ public void testEarliestValidTimestamp_GivenDataCountsAndLatency() {
580580
assertThat(builder.build().earliestValidTimestamp(dataCounts), equalTo(123455789L));
581581
}
582582

583+
public void testCopyingJobDoesNotCauseStackOverflow() {
584+
Job job = createRandomizedJob();
585+
for (int i = 0; i < 100000; i++) {
586+
job = new Job.Builder(job).build();
587+
}
588+
}
589+
583590
public static Job.Builder buildJobBuilder(String id, Date date) {
584591
Job.Builder builder = new Job.Builder(id);
585592
builder.setCreateTime(date);

0 commit comments

Comments
 (0)