Problem/Motivation
In one of our websites we see the following frequent error message:
Error: Call to a member function hasRequirement() on null in Drupal\Core\EventSubscriber\CsrfExceptionSubscriber->on403() (regel 36 van /path/to/project/web/core/lib/Drupal/Core/EventSubscriber/CsrfExceptionSubscriber.php) #0 /path/to/project/web/core/lib/Drupal/Core/EventSubscriber/HttpExceptionSubscriberBase.php(109): Drupal\Core\EventSubscriber\CsrfExceptionSubscriber->on403()
#1 [internal function]: Drupal\Core\EventSubscriber\HttpExceptionSubscriberBase->onException()
#2 /path/to/project/web/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(111): call_user_func()
#3 /path/to/project/vendor/symfony/http-kernel/HttpKernel.php(239): Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch()
#4 /path/to/project/vendor/symfony/http-kernel/HttpKernel.php(91): Symfony\Component\HttpKernel\HttpKernel->handleThrowable()
#5 /path/to/project/web/core/lib/Drupal/Core/StackMiddleware/Session.php(53): Symfony\Component\HttpKernel\HttpKernel->handle()
#6 /path/to/project/web/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\Core\StackMiddleware\Session->handle()
#7 /path/to/project/web/core/lib/Drupal/Core/StackMiddleware/ContentLength.php(28): Drupal\Core\StackMiddleware\KernelPreHandle->handle()
#8 /path/to/project/web/core/modules/ban/src/BanMiddleware.php(50): Drupal\Core\StackMiddleware\ContentLength->handle()
#9 /path/to/project/web/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\ban\BanMiddleware->handle()
#10 /path/to/project/web/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle()
#11 /path/to/project/web/core/lib/Drupal/Core/StackMiddleware/AjaxPageState.php(36): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle()
#12 /path/to/project/web/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\Core\StackMiddleware\AjaxPageState->handle()
#13 /path/to/project/web/core/lib/Drupal/Core/DrupalKernel.php(741): Drupal\Core\StackMiddleware\StackedHttpKernel->handle()
#14 /path/to/project/web/index.php(28): Drupal\Core\DrupalKernel->handle()
#15 {main}.
The problem seems to originate in the ban module. I have not yet been able to reproduce the problem / cause.
However, I think the fix should lie in the CsrfExceptionSubscriber itself. The following code expects $route to be an object, but $routeMatch->getRouteObject() may return NULL.
public function on403(ExceptionEvent $event): void {
$request = $event->getRequest();
$routeMatch = RouteMatch::createFromRequest($request);
$route = $routeMatch->getRouteObject();
if (!$route->hasRequirement('_csrf_token') || empty($route->getOption('_csrf_confirm_form_route'))) {
return;
}
$event->setResponse(new RedirectResponse(Url::fromRoute($route->getOption('_csrf_confirm_form_route'))->toString()));
}
Steps to reproduce
To reproduce install the module "crash403" attached and call an URL on your Drupal site that ends with "/crash".
Proposed resolution
I think the solution could be as simple as:
$route = $routeMatch->getRouteObject();
if (is_null($route) || !$route->hasRequirement('_csrf_token') || empty($route->getOption('_csrf_confirm_form_route'))) {
return;
}
Remaining tasks
Fix the issue in Version 10.3 and up. The issue exists is 11 as well.
User interface changes
-
Introduced terminology
-
API changes
-
Data model changes
-
Release notes snippet
-
| Comment | File | Size | Author |
|---|---|---|---|
| #10 | crash403.zip | 1.36 KB | rgpublic |
| #3 | route-is-sometimes-null-3511584.patch | 909 bytes | tuan.hmt |
Issue fork drupal-3511584
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3511584-route-is-sometimes
changes, plain diff MR !15829
Comments
Comment #2
quietone commentedChanging tags per Issue tags field and Issue tags -- special tags
Comment #3
tuan.hmt commentedpatch to fix for drupal 10.3.x
Comment #4
astonvictor commentedworks for me
thanks in advance
Comment #5
smustgrave commentedFixed need to be in MRs.
Also this will need steps to reproduce and probably test coverage
Comment #6
astonvictor commentedrefs to steps
I had an error when trying to replace 404 response with 403 response for some cases.
e.g. when a user tries to access /user/2 page, but user 2 was removed.
to replace the response, I used an event subscriber with
$events[KernelEvents::EXCEPTION]eventwith a callback smth like :
Comment #7
rgpublicThis can happen when you throw an Access Denied exception from a URL that doesn't have a route. That can happen if you write a module that triggers this during an event subscriber. The premises in the original code, that such an exception must only occur when the current page is routed is too strict.
Comment #8
berenddeboer commentedI'm also frequently seeing that, easily repeatable when hammering server with e2e tests, but triggered by different paths. Patch seems to work for me.
Comment #9
smustgrave commentedStill needs steps added to the summary
Comment #10
rgpublicI've added a module that can easily reproduce the crash. It happens as soon as some module triggers an AccessDeniedHttpException before a route is established or on pages that don't even have a route. My example just blocks access to all pages which URL ends with /crash. Of course this is arbitrary. It could be a module that wants to deny access on specific date ranges etc. The error message from the original issue description points to a ban module. I don't use that module myself, but I expect that it somehow bans or blocks access depending on IP etc. This is basically the same thing. As soon as that module intercepts a page request via EventSubscriber onKernelRequest and triggers an AccessDeniedHttpException in specific cases, the error appears. As I've already explained, the premesis that a route always exists whenever an AccessDeniedHttpException is triggered is incorrect. The fix/patch is obviously easy and has already been proposed here above.
Comment #11
smustgrave commentedThanks for providing an example, but typically the steps are more basic for how this is triggered in just core.
But fixes should be in MRs and bugs will need test coverage showing the issue.
Comment #12
rgpublic@smustgrave: I'm unsure on how to proceed here. It's quite a dilemma. The error can't be triggered with core alone. It can only be triggered with an additional event subscriber. But the existence of this bug prevents any modules from being able to throw AccessDeniedExceptions in EventSubcriber.
Comment #13
rgpublicAh, I just saw the ban module is part of core. We have this:
The error will probably appear as soon as isBanned() returns true. So we would need a test that simulates a banned IP and then this error will appear...
Comment #14
rgpublicThinking again, I discovered the ban module is about to be moved out of core and this issue doesn't really have anything to do with it. It therefore doesnt make sense to base any testing on the ban module either. I've filed a new issue #3566351 as feature request. I'll see whether I can manage to create a test for core. I've never written one, so it might be a bit difficult.
Comment #16
kristiaanvandeneyndeJust ran into this myself and #12 is right.
We have this on a jsonapi resource (which is core code) and a custom AuthenticationProviderInterface throwing an AccessDeniedHttpException. The jsonapi resource does not seem to add a route object to the request, which leads to the error in the IS.
Comment #17
mstrelan commentedUpdating the component as per #14 and #16 this is not specific to ban.module
Comment #20
scott_euser commentedLooking at 'main' branch there is now an is_null($route) via #3566351: Throwing AccessDeniedException without a route causes PHP errors