Problem/Motivation

There is/was a check in FileWidgetAjaxController which protected against broken POST requests.
This could happen in case file uploads are too big so that the POST request body gets truncated

    $form_parents = explode('/', $request->query->get('element_parents'));
    $form_build_id = $request->query->get('form_build_id');
    $request_form_build_id = $request->request->get('form_build_id');

    if (empty($request_form_build_id) || $form_build_id !== $request_form_build_id) {
      // Invalid request.
      drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error');
      $response = new AjaxResponse();
      $status_messages = array('#type' => 'status_messages');
      return $response->addCommand(new ReplaceCommand(NULL, $this->renderer->renderRoot($status_messages)));
    }

Proposed resolution

Ensure that the form build ID is rendered as early as possible.

Possible ways:

  • Ensure that its documentation that we want to render it first
  • Decrease the weight of the $form['form_build_id'] element as much as possible
  • Render it early in the template, and document that templates should render it first

Remaining tasks

User interface changes

API changes

CommentFileSizeAuthor
#3 2507019-2.patch1.04 KBeffulgentsia
#1 2507019-1.patch1.24 KBeffulgentsia
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

effulgentsia’s picture

Status: Active » Needs review
FileSize
1.24 KB

There is/was a check in FileWidgetAjaxController which protected against broken POST requests.

Firstly, curious if there's any test coverage for it. If not, this will pass, which I suspect is the case.

effulgentsia’s picture

Title: Ensure that $form['form_build_id'] is rendered first » Determine how much POST truncation protection is needed and add tests for it

Secondly, I manually tested (on PHP 5.5 on MAMP) uploading a file that's bigger than php.ini's post_max_size. Regardless of whether upload_max_filesize is bigger, equal, or smaller than post_max_size, the behavior is the same, which is that all of $_POST is empty, regardless of whether the other elements are before or after the file element. That is consistent with PHP's documented behavior.

So, retitling this issue, since it's not necessarily a settled conclusion that the order of the element matters.

However, I did not test situations of a request body truncated prior to PHP, such as from an aborted connected from the client or something like suhosin on the server. It's possible that order matters in such a case, in which case, we need to decide whether we want the element coming through successfully (by ordering it before the file element) or not (by ordering it after). Or maybe, we want 'form_build_id' early in the form and some other element (e.g., 'form_truncation_check') late in the form. Before going down that road of messing with order through, let's see if PHP takes care of things for us, by e.g., automatically detecting if the request body length is less than the Content-Length header and giving us an empty $_POST for us (wouldn't that be nice).

If the only purpose of checking $form_build_id is to cover the case of an empty $_POST due to a file larger than post_max_size, then it's unnecessary to compare it against the one from the query string. This patch should provide the same protection, but allow removal of the query string one, which is necessary for #2500527: Rewrite \Drupal\file\Controller\FileWidgetAjaxController::upload() to not rely on form cache.

effulgentsia’s picture

FileSize
1.04 KB

Patch for #2.

effulgentsia’s picture

Also, the current protection being only in FileWidgetAjaxController is problematic due to:

  • It's possible to submit forms that exceed the post_max_size, even without files, though I guess that's much less common.
  • This code does not run for people without JS. I tried creating an article with JS disabled, and when I try to upload a file > post_max_size, I just get back a redisplay of an empty form, with no friendly message of any kind, because without anything in $_POST, the form is processed identically as during the GET.
dawehner’s picture

If I understood your previous comments is that the point is to protected against the case
where your uploaded file has nearly post_max_size / so there is no not enough bytes left for the full other rest of the form data ...

effulgentsia’s picture

so there is no not enough bytes left for the full other rest of the form data

No, I think even there PHP will empty out all of $_POST. The only thing I'm unsure of is what happens when it's not PHP that applies post_max_size, but something prior to PHP truncating the request body. Then the request body length will be less than the 'Content-Length' header, but I don't know if PHP gives you an empty $_POST for that, or ignores the header and parses the incomplete body anyway. If the latter, then we may need a hidden input element at the end of the form to detect that.

catch’s picture

Adding related issue. A long time since I've looked at that one, but that also results in silent truncation (or did at the time that issue was active).

dawehner’s picture

Did a little bit of manual research. I copied the file upload request to CURL and manipulated sent POST data.

  1. Removed Content-Length: PHP did not complained about it. Then I removed the last 50 chars from the POST data (ajaxPageData[libraries]). PHP then still returned you everything before it.
  2. Replaced Content-Length with a higher value: PHP did not complained about it either, at all.
  3. Replaced Content-Length with a lower value: (10000 => 1000) In that case the request is indeed truncated.

Given that Content-Length is optional and in case is just used to determine how much bytes of the request is read.

Damien Tournoud’s picture

There seem to be a lot of misunderstandings here. An HTTP server really should not process an incomplete HTTP message. If something in the chains allows an incomplete HTTP message to be processed, it is a bug in that something.

Can we understand what is happening and identify which infrastructure that are affected? Do we have a setup somewhere that is affected?

Damien Tournoud’s picture

For information, RFC7230 defines an incomplete message as:

A message body that uses the chunked transfer coding is incomplete if the zero-sized chunk that terminates the encoding has not been received. A message that uses a valid Content-Length is incomplete if the size of the message body received (in octets) is less than the value given by Content-Length. A response that has neither chunked transfer coding nor Content-Length is terminated by closure of the connection and, thus, is considered complete regardless of the number of message body octets received, provided that the header section was received intact.

Damien Tournoud’s picture

About @dawehner#8:

Removed Content-Length: PHP did not complained about it. Then I removed the last 50 chars from the POST data (ajaxPageData[libraries]). PHP then still returned you everything before it.

^ This is not possible. You cannot submit an HTTP message body without either a Content-Length or using a chunked transfer encoding (which carries the length information by itself).

Replaced Content-Length with a higher value: PHP did not complained about it either, at all.

^ This is not possible, or it means that you are actually using chunked transfer encoding without knowing it. If you were not using chunked transfer encoding, the request would just block until there is sufficient data.

Replaced Content-Length with a lower value: (10000 => 1000) In that case the request is indeed truncated.

^ That is expected.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

jhedstrom’s picture

Status: Needs review » Postponed (maintainer needs more info)
Issue tags: +Needs issue summary update

This has gone a long time without comment, and there is much discussion about the direction since the last posted patch.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Status: Postponed (maintainer needs more info) » Closed (outdated)