Merged PR 6773: Fix routing policy exit destination

**Description**

An infinite loop can happen in routing if there is a catch all route with host name matching.

This problem is caused by the DFA matcher builder giving an incorrect exit destination to policies. Currently the exit destination is the catch all state, so the policy will transition to itself when there is no match. It will run again, transition to itself again, run again, etc. This causes the policy to run forever.

What should happen is the host name policy fails, it transitions to the final state with no candidates, and the route matcher does not match any endpoints. The browser is returned a 404 status.

**Customer Impact**

This problem shows up in this situation:
1. If a customer has configured a catch all route in their app
2. The catch all route has host matching
3. A browser makes a request to the server that matches the catch all route but doesn't match the host name

The route matcher will run forever, using up a threadpool thread. When threadpool threads are exhausted the server will stop responding.

**Regression?**

No.

**Risk**

Medium. The fix is simple but route matching is complex, and routing runs with every request.
This commit is contained in:
James Newton-King 2020-04-02 20:30:18 +00:00 committed by Andrew Stanton-Nurse
parent 8a4133b34f
commit b53179267a
2 changed files with 37 additions and 2 deletions

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
@ -429,7 +429,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
candidates,
endpointSelectorPolicies?.ToArray() ?? Array.Empty<IEndpointSelectorPolicy>(),
JumpTableBuilder.Build(currentDefaultDestination, currentExitDestination, pathEntries),
BuildPolicy(currentExitDestination, node.NodeBuilder, policyEntries));
// Use the final exit destination when building the policy state.
// We don't want to use either of the current destinations because they refer routing states,
// and a policy state should never transition back to a routing state.
BuildPolicy(exitDestination, node.NodeBuilder, policyEntries));
return currentStateIndex;

View File

@ -273,6 +273,38 @@ namespace Microsoft.AspNetCore.Routing.Matching
MatcherAssert.AssertMatch(httpContext, endpoint);
}
[Fact]
public async Task Match_CatchAllRouteWithMatchingHost_Success()
{
// Arrange
var endpoint = CreateEndpoint("/{**path}", hosts: new string[] { "contoso.com", });
var matcher = CreateMatcher(endpoint);
var httpContext = CreateContext("/hello", "contoso.com");
// Act
await matcher.MatchAsync(httpContext);
// Assert
MatcherAssert.AssertMatch(httpContext, endpoint, new { path = "hello" });
}
[Fact]
public async Task Match_CatchAllRouteFailureHost_NoMatch()
{
// Arrange
var endpoint = CreateEndpoint("/{**path}", hosts: new string[] { "contoso.com", });
var matcher = CreateMatcher(endpoint);
var httpContext = CreateContext("/hello", "nomatch.com");
// Act
await matcher.MatchAsync(httpContext);
// Assert
MatcherAssert.AssertNotMatch(httpContext);
}
private static Matcher CreateMatcher(params RouteEndpoint[] endpoints)
{
var services = new ServiceCollection()