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

-

Issue fork drupal-3511584

Command icon 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:

Comments

paul dudink created an issue. See original summary.

quietone’s picture

Version: 10.3.x-dev » 11.x-dev
tuan.hmt’s picture

StatusFileSize
new909 bytes

patch to fix for drupal 10.3.x

astonvictor’s picture

Status: Active » Reviewed & tested by the community

works for me

thanks in advance

smustgrave’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs steps to reproduce

Fixed need to be in MRs.

Also this will need steps to reproduce and probably test coverage

astonvictor’s picture

refs 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] event
with a callback smth like :

if ($event->getThrowable() instanceof NotFoundHttpException && 'user/2' == $path) {
  $event->setThrowable(new AccessDeniedHttpException());
}
rgpublic’s picture

This 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.

berenddeboer’s picture

Status: Needs work » Needs review

I'm also frequently seeing that, easily repeatable when hammering server with e2e tests, but triggered by different paths. Patch seems to work for me.

smustgrave’s picture

Status: Needs review » Needs work

Still needs steps added to the summary

rgpublic’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new1.36 KB

I'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.

smustgrave’s picture

Status: Needs review » Needs work

Thanks 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.

rgpublic’s picture

@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.

rgpublic’s picture

Ah, I just saw the ban module is part of core. We have this:

  public function handle(Request $request, $type = self::MAIN_REQUEST, $catch = TRUE): Response {
    $ip = $request->getClientIp();
    if ($this->banIpManager->isBanned($ip)) {
      return new Response(new FormattableMarkup('@ip has been banned', ['@ip' => $ip]), 403);
    }
    return $this->httpKernel->handle($request, $type, $catch);
  }

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...

rgpublic’s picture

Thinking 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.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

kristiaanvandeneynde’s picture

Just 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.

mstrelan’s picture

Component: ban.module » base system

Updating the component as per #14 and #16 this is not specific to ban.module

scott_euser made their first commit to this issue’s fork.

scott_euser’s picture

Status: Needs work » Closed (duplicate)

Looking at 'main' branch there is now an is_null($route) via #3566351: Throwing AccessDeniedException without a route causes PHP errors

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.