Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Field-level security with inner_hits can cause index out-of-bounds exception #30624

Closed
james-uffindell-granta opened this issue May 15, 2018 · 3 comments
Assignees
Labels
>bug :Security/Authorization Roles, Privileges, DLS/FLS, RBAC/ABAC

Comments

@james-uffindell-granta
Copy link

Elasticsearch version (bin/elasticsearch --version):
Version: 6.2.4, Build: ccec39f/2018-04-12T20:37:28.497551Z

Plugins installed:
x-pack
x-pack-core
x-pack-deprecation
x-pack-graph
x-pack-logstash
x-pack-ml
x-pack-monitoring
x-pack-security
x-pack-upgrade
x-pack-watcher

JVM version (java -version):
java 9
Java(TM) SE Runtime Environment (build 9+181)
Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)

OS version (uname -a if on a Unix-like system):
Windows 10

Description of the problem including expected versus actual behavior:
Running a query with field-level security on a subfield of a nested array field and asking for inner_hits can cause an index out of bounds exception.

Expected behavior: no error. (If this is a known limitation of either inner_hits or field-level security then I couldn't see it in the documentation.)

Steps to reproduce:

# create a new index
curl -XPUT localhost:9200/test

# define a mapping
curl -XPUT localhost:9200/test/_mapping/document -d '{
    "properties": {
        "document_name": {
            "type": "text"
        },
        "metadata": {
            "type": "nested",
            "properties": {
                "name": {"type": "text"},
                "sv": {"type": "text"},
                "nv": {"type": "double"}
            }
        }
    }
}'

# index a document:
# it has one 'metadata' value where nv is populated and another where sv is populated
curl -XPOST localhost:9200/test/document -d '{
    "document_name": "testing",
    "metadata": [
        {
            "name": "numeric",
            "nv": 0.2
        },
        {
            "name": "string",
            "sv": "problem"
        }
    ]
}'

# run this query; it should retrieve the document okay including the inner_hits
# metadata[1] is the inner hit which matches; metadata[0] doesn't match
curl -XPOST localhost:9200/test/_search -d '{
    "query": {
        "bool": {
            "must": [
                {
                    "nested": {
                        "path": "metadata",
                        "inner_hits":{},
                        "score_mode": "avg",
                        "query": {
                            "bool": {
                                "must": [
                                    {
                                        "match": {
                                            "metadata.sv": "problem"
                                        }
                                    }
                                ]
                            }
                        }
                    }
                }
            ]
        }
    }
}'

# create a role which can only see metadata.sv
curl -XPUT localhost:9200/_xpack/security/role/reproduce -d '{
    "indices": [
        {
            "names": [ "test" ],
            "privileges": [ "read" ],
            "field_security" : {
                "grant": [ "metadata.sv" ]
            }
        }    
    ]
}'

# create a user in that role
curl -XPUT localhost:9200/_xpack/security/user/repro-user -d '{
    "roles": ["reproduce"],
    "password": "whatever",
    "full_name": "Repro User",
    "email": "blah@blah.blah"
}'

Now, if you rerun the previous query but authenticating as this new user, instead of getting results, it fails:

"failures": [
    {
        ...
        "reason": {
            "type": "index_out_of_bounds_exception",
            "reason": "Index 1 out-of-bounds for length 1"
        }
    }
]

Removing "inner_hits":{} from the query will make it work.

My guess is that it figures out that metadata[1] is the inner hit which matches, then strips out the fields you aren't allowed to see, which removes metadata[0] since it doesn't have a metadata.sv field, which leaves the metadata array one element long instead of two, so when it then looks up metadata[1] to put it in the inner hits node, it goes bang?

@jbaiera jbaiera added the :Security/Authorization Roles, Privileges, DLS/FLS, RBAC/ABAC label May 15, 2018
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-security

@jbaiera jbaiera added the >bug label May 15, 2018
@james-uffindell-granta
Copy link
Author

There's what seems to be a related issue where you don't get an error, but you do get the wrong inner hit back.

If you repeat the above steps, but instead index the document

{
	"document_name": "followup testing",
	"metadata": [
		{
			"name": "numeric",
			"nv": 0.1
		},
		{
			"name": "string",
			"sv": "problem"
		},
		{
			"name": "second",
			"sv": "kitten"
		}
	]
}

and rerun the search which throws an exception, you no longer get an exception, but the "inner_hits" part of the response contains the "kitten" metadata field instead of the "problem" one which you actually searched for.

This seems consistent with the guess that it locates the array index of the inner hit before doing the filtering, and looks it up properly afterwards.

@martijnvg
Copy link
Member

@james-uffindell-granta Thanks for reporting this problem. The inner hit source filtering is not working correctly with field level security.

Stacktrace of the first request:

[elasticsearch] [2018-05-16T17:56:48,844][WARN ][r.suppressed             ] path: /test/_search, params: {index=test}
[elasticsearch] org.elasticsearch.action.search.SearchPhaseExecutionException: all shards failed
[elasticsearch]         at org.elasticsearch.action.search.AbstractSearchAsyncAction.onPhaseFailure(AbstractSearchAsyncAction.java:293) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.action.search.AbstractSearchAsyncAction.executeNextPhase(AbstractSearchAsyncAction.java:133) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.action.search.AbstractSearchAsyncAction.onPhaseDone(AbstractSearchAsyncAction.java:254) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.action.search.InitialSearchPhase.onShardFailure(InitialSearchPhase.java:101) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.action.search.InitialSearchPhase.access$100(InitialSearchPhase.java:48) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.action.search.InitialSearchPhase$2.lambda$onFailure$1(InitialSearchPhase.java:222) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.action.search.InitialSearchPhase.maybeFork(InitialSearchPhase.java:176) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.action.search.InitialSearchPhase.access$000(InitialSearchPhase.java:48) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.action.search.InitialSearchPhase$2.onFailure(InitialSearchPhase.java:222) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.action.search.SearchExecutionStatsCollector.onFailure(SearchExecutionStatsCollector.java:73) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.action.ActionListenerResponseHandler.handleException(ActionListenerResponseHandler.java:51) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.action.search.SearchTransportService$ConnectionCountingHandler.handleException(SearchTransportService.java:535) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.transport.TransportService$ContextRestoreResponseHandler.handleException(TransportService.java:1095) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.transport.TransportService$DirectResponseChannel.processException(TransportService.java:1188) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.transport.TransportService$DirectResponseChannel.sendResponse(TransportService.java:1172) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.transport.TaskTransportChannel.sendResponse(TaskTransportChannel.java:66) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.action.search.SearchTransportService$6$1.onFailure(SearchTransportService.java:393) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.search.SearchService$2.onFailure(SearchService.java:340) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.search.SearchService$2.onResponse(SearchService.java:334) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.search.SearchService$2.onResponse(SearchService.java:328) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.search.SearchService$3.doRun(SearchService.java:1015) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingAbstractRunnable.doRun(ThreadContext.java:724) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.common.util.concurrent.TimedRunnable.doRun(TimedRunnable.java:41) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135) [?:?]
[elasticsearch]         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) [?:?]
[elasticsearch]         at java.lang.Thread.run(Thread.java:844) [?:?]
[elasticsearch] Caused by: org.elasticsearch.ElasticsearchException$1: Index 1 out-of-bounds for length 1
[elasticsearch]         at org.elasticsearch.ElasticsearchException.guessRootCauses(ElasticsearchException.java:658) ~[elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.action.search.AbstractSearchAsyncAction.executeNextPhase(AbstractSearchAsyncAction.java:131) [elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         ... 26 more
[elasticsearch] Caused by: java.lang.IndexOutOfBoundsException: Index 1 out-of-bounds for length 1
[elasticsearch]         at jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64) ~[?:?]
[elasticsearch]         at jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70) ~[?:?]
[elasticsearch]         at jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248) ~[?:?]
[elasticsearch]         at java.util.Objects.checkIndex(Objects.java:372) ~[?:?]
[elasticsearch]         at java.util.ArrayList.get(ArrayList.java:440) ~[?:?]
[elasticsearch]         at org.elasticsearch.search.fetch.FetchPhase.createNestedSearchHit(FetchPhase.java:295) ~[elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.search.fetch.FetchPhase.execute(FetchPhase.java:153) ~[elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.search.fetch.subphase.InnerHitsFetchSubPhase.hitsExecute(InnerHitsFetchSubPhase.java:69) ~[elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.search.fetch.FetchPhase.execute(FetchPhase.java:170) ~[elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.search.SearchService.executeFetchPhase(SearchService.java:392) ~[elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.search.SearchService.executeQueryPhase(SearchService.java:367) ~[elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]
[elasticsearch]         at org.elasticsearch.search.SearchService$2.onResponse(SearchService.java:332) ~[elasticsearch-7.0.0-alpha1-SNAPSHOT.jar:7.0.0-alpha1-SNAPSHOT]

@martijnvg martijnvg self-assigned this May 17, 2018
martijnvg added a commit to martijnvg/elasticsearch that referenced this issue May 22, 2018
…ters out all fields

Prior to this change an json array element with no fields would be omitted from json array.
Nested inner hits source filtering relies on the fact that the json array element numbering
remains untouched and this causes AOOB exceptions in the ES side during the fetch phase
without this change.

Closes elastic#30624
martijnvg added a commit that referenced this issue May 22, 2018
…ters out all fields (#30709)

Prior to this change an json array element with no fields would be omitted from json array.
Nested inner hits source filtering relies on the fact that the json array element numbering
remains untouched and this causes AOOB exceptions in the ES side during the fetch phase
without this change.

Closes #30624
martijnvg added a commit that referenced this issue May 22, 2018
…ters out all fields (#30709)

Prior to this change an json array element with no fields would be omitted from json array.
Nested inner hits source filtering relies on the fact that the json array element numbering
remains untouched and this causes AOOB exceptions in the ES side during the fetch phase
without this change.

Closes #30624
martijnvg added a commit that referenced this issue May 22, 2018
…ters out all fields (#30709)

Prior to this change an json array element with no fields would be omitted from json array.
Nested inner hits source filtering relies on the fact that the json array element numbering
remains untouched and this causes AOOB exceptions in the ES side during the fetch phase
without this change.

Closes #30624
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
>bug :Security/Authorization Roles, Privileges, DLS/FLS, RBAC/ABAC
Projects
None yet
Development

No branches or pull requests

4 participants