Skip to content

Commit d9f6887

Browse files
Fix DLS over Runtime Fields (#112260) (#112318)
There is a DLS query referencing a runtime field loaded from _source, when we create the collector manager we retrieve the numDocs which triggers going through all segments and executing the script for each document. StoredFieldSourceProvider relies on leaf ordinals to build an array, but those ordinals are not populated when computing the numDocs via BaseCompositeReader, as that goes through the subreaders contexts, and not the context leaves (there is a subtle difference that bites us there). Fixes #111637
1 parent f2babe7 commit d9f6887

File tree

3 files changed

+59
-30
lines changed

3 files changed

+59
-30
lines changed

docs/changelog/112260.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 112260
2+
summary: Fix DLS over Runtime Fields
3+
area: "Authorization"
4+
type: bug
5+
issues:
6+
- 111637

server/src/main/java/org/elasticsearch/search/lookup/StoredFieldSourceProvider.java

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,53 +8,36 @@
88

99
package org.elasticsearch.search.lookup;
1010

11-
import org.apache.lucene.index.IndexReaderContext;
1211
import org.apache.lucene.index.LeafReaderContext;
12+
import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
1313
import org.elasticsearch.index.fieldvisitor.LeafStoredFieldLoader;
1414
import org.elasticsearch.index.fieldvisitor.StoredFieldLoader;
1515

1616
import java.io.IOException;
17+
import java.util.Map;
1718

1819
// NB This is written under the assumption that individual segments are accessed by a single
1920
// thread, even if separate segments may be searched concurrently. If we ever implement
2021
// within-segment concurrency this will have to work entirely differently.
2122
class StoredFieldSourceProvider implements SourceProvider {
2223

2324
private final StoredFieldLoader storedFieldLoader;
24-
private volatile LeafStoredFieldSourceProvider[] leaves;
25+
private final Map<Object, LeafStoredFieldSourceProvider> leaves = ConcurrentCollections.newConcurrentMap();
2526

2627
StoredFieldSourceProvider(StoredFieldLoader storedFieldLoader) {
2728
this.storedFieldLoader = storedFieldLoader;
2829
}
2930

3031
@Override
3132
public Source getSource(LeafReaderContext ctx, int doc) throws IOException {
32-
LeafStoredFieldSourceProvider[] leaves = getLeavesUnderLock(findParentContext(ctx));
33-
if (leaves[ctx.ord] == null) {
34-
// individual segments are currently only accessed on one thread so there's no need
35-
// for locking here.
36-
leaves[ctx.ord] = new LeafStoredFieldSourceProvider(storedFieldLoader.getLoader(ctx, null));
33+
final Object id = ctx.id();
34+
var provider = leaves.get(id);
35+
if (provider == null) {
36+
provider = new LeafStoredFieldSourceProvider(storedFieldLoader.getLoader(ctx, null));
37+
var existing = leaves.put(id, provider);
38+
assert existing == null : "unexpected source provider [" + existing + "]";
3739
}
38-
return leaves[ctx.ord].getSource(doc);
39-
}
40-
41-
private static IndexReaderContext findParentContext(LeafReaderContext ctx) {
42-
if (ctx.parent != null) {
43-
return ctx.parent;
44-
}
45-
assert ctx.isTopLevel;
46-
return ctx;
47-
}
48-
49-
private LeafStoredFieldSourceProvider[] getLeavesUnderLock(IndexReaderContext parentCtx) {
50-
if (leaves == null) {
51-
synchronized (this) {
52-
if (leaves == null) {
53-
leaves = new LeafStoredFieldSourceProvider[parentCtx.leaves().size()];
54-
}
55-
}
56-
}
57-
return leaves;
40+
return provider.getSource(doc);
5841
}
5942

6043
private static class LeafStoredFieldSourceProvider {

x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/integration/DocumentLevelSecurityRandomTests.java

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@
1313
import org.elasticsearch.index.query.QueryBuilders;
1414
import org.elasticsearch.test.SecurityIntegTestCase;
1515
import org.elasticsearch.test.SecuritySettingsSourceField;
16+
import org.elasticsearch.xcontent.XContentFactory;
1617
import org.elasticsearch.xpack.core.XPackSettings;
18+
import org.junit.BeforeClass;
1719

1820
import java.util.ArrayList;
1921
import java.util.Collections;
2022
import java.util.List;
2123

2224
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
25+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
2326
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertResponse;
2427
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.BASIC_AUTH_HEADER;
2528
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
@@ -29,9 +32,12 @@ public class DocumentLevelSecurityRandomTests extends SecurityIntegTestCase {
2932

3033
protected static final SecureString USERS_PASSWD = SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING;
3134

32-
// can't add a second test method, because each test run creates a new instance of this class and that will will result
33-
// in a new random value:
34-
private final int numberOfRoles = scaledRandomIntBetween(3, 99);
35+
private static volatile int numberOfRoles;
36+
37+
@BeforeClass
38+
public static void setupRoleCount() throws Exception {
39+
numberOfRoles = scaledRandomIntBetween(3, 99);
40+
}
3541

3642
@Override
3743
protected String configUsers() {
@@ -119,4 +125,38 @@ public void testDuelWithAliasFilters() throws Exception {
119125
}
120126
}
121127

128+
public void testWithRuntimeFields() throws Exception {
129+
assertAcked(
130+
indicesAdmin().prepareCreate("test")
131+
.setMapping(
132+
XContentFactory.jsonBuilder()
133+
.startObject()
134+
.startObject("runtime")
135+
.startObject("field1")
136+
.field("type", "keyword")
137+
.endObject()
138+
.endObject()
139+
.startObject("properties")
140+
.startObject("field2")
141+
.field("type", "keyword")
142+
.endObject()
143+
.endObject()
144+
.endObject()
145+
)
146+
);
147+
List<IndexRequestBuilder> requests = new ArrayList<>(47);
148+
for (int i = 1; i <= 42; i++) {
149+
requests.add(prepareIndex("test").setSource("field1", "value1", "field2", "foo" + i));
150+
}
151+
for (int i = 42; i <= 57; i++) {
152+
requests.add(prepareIndex("test").setSource("field1", "value2", "field2", "foo" + i));
153+
}
154+
indexRandom(true, requests);
155+
assertHitCount(
156+
client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD)))
157+
.prepareSearch("test"),
158+
42L
159+
);
160+
}
161+
122162
}

0 commit comments

Comments
 (0)