Problem/Motivation

Symfony providers two mime guessers FileinfoMimeTypeGuesser and FileBinaryMimeTypeGuesser which do a better and more secure job than ExtensionMimeTypeGuesser.

If either are supported we should add them to Drupal\Core\File\MimeType\MimeTypeGuesser with a preference for FileinfoMimeTypeGuesser.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue priority Major because using fileinfo or the file command represents a security improvement over the extension based guessing.
Prioritized changes The main goal of this issue is security.
Disruption Whilst this removes a hook from core this hook is only used by the File Entity module in Drupal 7 (Looking at http://drupalcontrib.org/api/drupal/drupal%21modules%21system%21system.a...). All the additional maapins it adds are in Drupal 8 so this is not necessary since #1443070: Add support for popular e-book formats, Google web formats, mkv and mka in file_default_mimetype_mapping() landed.

Proposed resolution

Remaining tasks

Contributor tasks needed
Task Novice task? Contributor instructions Complete?
Fix file upload when new MIME type guessers are enabled See details in #2388749-72: Register symfony's mime guessers if they are supported
Reroll the patch if it no longer applies. Instructions

User interface changes

API changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

alexpott’s picture

Status: Active » Needs review
FileSize
1.86 KB
dawehner’s picture

duplicate key in the core.services.yml file.

alexpott’s picture

Status: Needs work » Needs review
FileSize
485 bytes
1.86 KB

doh.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Looks perfect from my perspective

alexpott’s picture

Status: Needs work » Needs review
FileSize
671 bytes
1.79 KB

lolz - c&p fail

alexpott’s picture

Status: Needs work » Needs review
FileSize
4.98 KB
6.77 KB

So file_get_mimetype used to accept just a filename and not a complete path. Since we have moved to Symfony's MimeTypeGuesserInterface we should completely remove this function as it is not only deprecated it's documentation is misleading and incorrect - you can no longer add mappings.

alexpott’s picture

Issue summary: View changes
FileSize
25.29 KB
19.09 KB

The original issue to move to Symfony's mime guessing #1921558: Convert file_get_mimetype() to use Symfony MimeTypeGuessers left the ability to alter mappings and set mappings. This does not make sense. If a module needs to add additional guessers it should just add a new service tagged with mime_type_guesser. This patch removes hook_file_mimetype_mapping_alter().

alexpott’s picture

Status: Needs work » Needs review
alexpott’s picture

FileSize
477 bytes
25.26 KB

Missing a new line

The last submitted patch, 10: 2388749.10.patch, failed testing.

alexpott’s picture

#2311679: Separate MIME type mapping from ExtensionMimeTypeGuesser is related. The expectation to be able to list all the supported mime types is interesting. D8 has not had this ability for a while.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community
+++ b/core/tests/Drupal/Tests/Core/File/MimeTypeGuesserTest.php
@@ -14,11 +14,40 @@
+    $guesser_1 = $this->getMock('Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface');
+    $guesser_1->expects($this->once())
+      ->method('guess')
+      ->with('file.txt')
+      ->willReturn('text/plain');
+    $mime_guesser_service->addGuesser($guesser_1);
+    $this->assertEquals('text/plain', $mime_guesser_service->guess('file.txt'));
+    $guesser_2 = $this->getMock('Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface');
+    $guesser_2->expects($this->once())
+      ->method('guess')
+      ->with('file.txt')
+      ->willReturn('text/x-diff');
+    $mime_guesser_service->addGuesser($guesser_2, 10);
+    $this->assertEquals('text/x-diff', $mime_guesser_service->guess('file.txt'));

In an ideal world, we would ensure here that we care about the order. http://docs.mockery.io/en/latest/reference/expectations.html allows this really nicely to do.

alexpott’s picture

The order is tested. The test adds one guessers and makes an assertion. Then is adds another guesser and makes an assertion on the same file path but the new guesser (higher priority) changes the expected result.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 12: 2388749.11.patch, failed testing.

Status: Needs work » Needs review

basic queued 12: 2388749.11.patch for re-testing.

alexpott’s picture

Status: Needs review » Reviewed & tested by the community

Some testbot weirdness... back to rtbc as per #15

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 12: 2388749.11.patch, failed testing.

YesCT’s picture

Issue summary: View changes
Issue tags: +Needs reroll
adci_contributor’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
23.55 KB

I'm trying to reroll.
Please review

ParisLiakos’s picture

Issue tags: +Needs change record
+++ b/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php
@@ -865,40 +865,9 @@ class ExtensionMimeTypeGuesser implements MimeTypeGuesserInterface {
-      $this->moduleHandler->alter('file_mimetype_mapping', $mapping);

i agree with this, but it needs a change record

other than that its good to go

ParisLiakos’s picture

Status: Needs review » Needs work

Ah, seems #22 misses ExtensionMimeTypeGuesserTest from #12

adci_contributor’s picture

Status: Needs work » Needs review
FileSize
25.45 KB

Sorry, I missed an untracked file.
ExtensionMimeTypeGuesserTest returned in a patch.
And rerolled core.services.yml after #2379741: Add Renderer::getCacheableRenderArray() to encapsulate which data is needed for caching a render array and have views use it

YesCT’s picture

@adci_contributor
An interdiff between #22 and #25 would make it more likely you will get a review faster.
For instructions on creating an interdiff, see https://drupal.org/documentation/git/interdiff

adci_contributor’s picture

I rerolled #22 and returned ExtensionMimeTypeGuesserTest in the same patch, so i could not make interdiff between #22(which doesn't apply) and #25.
I guess we can just ignore #22, because #25 is actually a correct reroll of #12. Sorry again for this confusion.

ParisLiakos’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs change record

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 25: 2388749-25.patch, failed testing.

ParisLiakos’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
25.45 KB

just a reroll

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.0.x, thanks!

  • catch committed 4dae1d9 on 8.0.x
    Issue #2388749 by alexpott, adci_contributor, ParisLiakos: Register...
Dave Reid’s picture

Priority: Major » Critical
Status: Fixed » Needs work
Issue tags: +Needs change notice

This is missing a change notice for how to replace hook_file_mimetype_mapping_alter() for all conditions of adding, removing, and altering existing extension mappings. I don't agree with the removal of that hook either because it was also used to *fix* invalid mappings in core when it happened (which it did in Drupal 7's lifetime).

Dave Reid’s picture

Ok I see the change record mentions the hook was removed (which I don't think all use cases were fully justified in this issue before it was thrown out), but the examples need to be expanded on how to cover what hook_file_mimetype_mapping_alter() was used for before, rather than incredibly generic examples.

alexpott’s picture

The issue fixing invalid mappings will rarely be Drupal's responsibility in the future. The problem with the alter hook is that it makes it impossible to use the better and more secure services for mimetype guessing because how can we alter the response from PHP's fileinfo extension or the system file command. The whole point is that extensions are not a great way of determining a mimetype. In Drupal 7 there was only 1 module that used the hook to add new mappings - http://drupalcontrib.org/api/drupal/drupal%21modules%21system%21system.a...

If you want to an add additional mime type guesser or replace Drupal 8's extension mime guesser service that is entirely possible but I don't think it is the role of the CR to go into more detail than it does - we have enough trouble keeping CRs up-to-date with valid code samples for changes that affect 100's of modules. The existing CR details how to add a new guesser but it does not delve into the implementation because that is not its responsibility.

I think this issue should be reverted to major and fixed but I will wait for @Dave Reid to detail any other concerns.

Dave Reid’s picture

I don't see at all how supporting the alter hook makes it impossible to use the better and more secure services, especially after reading the patch that was committed.

alexpott’s picture

Because the alter hook would only affect the FIeExtensionGuesser - and so would only be used if you fallback to that guesser. Therefore, in order to guarantee that your alter is being used then you need to either remove the other mime guessing services or add a new service with a higher priority. In all ways you have to change services - so why not just add a new service.

Dave Reid’s picture

My assumption is that the alter hook only affects data for that specific service, not that it would automatically be used in favor of the other services. I say that as the person who's written the only module that implements this hook in D7.

mondrake’s picture

My 2 cents - can we think of adding something similar in #2311679: Separate MIME type mapping from ExtensionMimeTypeGuesser, but as a method of the MimeTypeMapper service rather than a hook? AFAICS the point here is not about determining the mime type of a 'real' file (the extension based guessing is becoming vestigial after this issue, there are two more prioritised guessers that take precedence), but rather about determining the mime type of a 'virtual' file based on its extension? And the mapping extension<->mimetype built-in in Drupal is a valuable asset that can be used via MimeTypeMapper methods, not guessers, introduced by the other issue.

alexpott’s picture

Priority: Critical » Major
Status: Needs work » Fixed
Issue tags: -Needs change notice

Also it is about the expectations of having the hook.

+++ b/core/modules/system/file.api.php
@@ -100,30 +100,6 @@ function hook_file_url_alter(&$uri) {
 /**
- * Alter MIME type mappings used to determine MIME type from a file extension.
- *
- * Invoked by \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::guess(). It
- * is used to allow modules to add to or modify the default mapping from
- * \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::$defaultMapping.

This is true if you only think about extension guessing but on many sites now that we are using the better and more secure services this just won't perform as the developer expects since the extension guesser is (correctly) the lowest priority. If a developer really wants to change the mimetype for all files ending with txt then they are going to have to register a new mime type guesser.

I would also argue that we should just be better at adding mime types to core - comparing Symfony's MimeTypeExtensionGuesser with ours - shows we've got a lot to add. We should have a commitment to keep up-to-date with https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types if we want to have this array in core.

I'm going to reset the issue status to fixed - any further discussions about the need for a hook can take place on #2311679: Separate MIME type mapping from ExtensionMimeTypeGuesser

mfb’s picture

Status: Fixed » Needs work

(Setting back to needs work because of major security vulnerability)

I'm very unclear why analyzing a file to determine its mimetype is considered "secure". To me, it's always been safer to assign the mime type based on file extension. The site will whitelist file extensions for a file upload field, and because each extension maps to a mime type, it's also whitelisting mime types. So it could easily prevent everything from malware to HTML from being uploaded.

Currently in Drupal 8, if I create a file field and allow .txt files to be uploaded, then create a file that starts with <?php and upload it, Drupal saves this as a text/x-php file. That seems sketchy, but maybe not easy to exploit.

Let's try another one, if I create a file that starts with <html> and upload it, Drupal saves it as a text/html file. When downloaded from the private filesystem, this file is downloaded as text/html and interpreted by the browser as HTML, which could contain javascript - this is a nice fat XSS vulnerability!

Aside from these simple examples, it's well-known that malicious files can be constructed that could be interpreted as a valid file of multiple different types. I didn't yet try these w/ Drupal 8.

BTW, see https://www.drupal.org/project/filemime for a drupal 7 contrib module that allows altering the built-in mime/extension mapping. I was planning to upgrade this to d8 at some point.

  • catch committed 5bf651d on 8.0.x
    Revert "Issue #2388749 by alexpott, adci_contributor, ParisLiakos:...
catch’s picture

Thanks mfb. I have a vague memory of us discussing this before a long time ago, and it may have been one of the original reasons we stuck to extension-based mime type guessing for so long. Would have been better to have remembered this before committing the patch...

Rolled this back for now.

We should:

1. Have a test for the file uploads/file downloads issue to avoid regressing.

2. Decide whether this problem is specific to file uploads (where we could possibly restrict to the extension guesser) or whether generally it's better not to do this.

alexpott’s picture

@mfb nice catch. Totally agree with rolling back here. I'm going to unpublish the CR.

alexpott’s picture

Version: 8.0.x-dev » 8.1.x-dev
Status: Needs work » Postponed

So given that we've obviously not achieved the desired benefits to security I think we should postpone this issue until we can. There are two parts to this. Firstly, determining what mimetype a file has. Secondly, determining what to allow to be uploaded. In HEAD these are linked because we use an extension based guesser. We then serve the file with guessed mimetype which protects us from the "nice fat XSS vulnerability".

I think we should postpone this, the amount of work to change file uploads to limit by actual mimetype and not extension is significant and unnecessarily risky and disruptive.

Any work on this patch need to add a test based on the security hole outlined by @mfb in #41

mfb’s picture

I see three things to consider:

  1. A way to whitelist allowed mime types. This could involve lots of permutations of mime types, due to multiple valid mime types for a file format or the preferred mime type changing over time.
  2. How do you deal with files that are not detected as one of the allowed mime types? You may want to reject the file, or you may want to forcibly set its mime type to one of the allowed mime types.
  3. A file could be saved by Drupal with one mime type, but served by the webserver with another mime type, because the webserver typically uses /etc/mime.types to determine the file's mime type.

  • catch committed 4dae1d9 on 8.1.x
    Issue #2388749 by alexpott, adci_contributor, ParisLiakos: Register...
  • catch committed 5bf651d on 8.1.x
    Revert "Issue #2388749 by alexpott, adci_contributor, ParisLiakos:...

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

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now 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.

  • catch committed 4dae1d9 on 8.3.x
    Issue #2388749 by alexpott, adci_contributor, ParisLiakos: Register...
  • catch committed 5bf651d on 8.3.x
    Revert "Issue #2388749 by alexpott, adci_contributor, ParisLiakos:...

  • catch committed 4dae1d9 on 8.3.x
    Issue #2388749 by alexpott, adci_contributor, ParisLiakos: Register...
  • catch committed 5bf651d on 8.3.x
    Revert "Issue #2388749 by alexpott, adci_contributor, ParisLiakos:...

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

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now 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.

BR0kEN’s picture

Status: Postponed » Needs review
FileSize
11.62 KB

Re-roll for 8.3.x with additional tests.

Status: Needs review » Needs work

The last submitted patch, 52: 2388749-52.patch, failed testing.

BR0kEN’s picture

Status: Needs work » Needs review
FileSize
13.22 KB
1.28 KB

Forgot to add new files.

BR0kEN’s picture

Also, as @mfb, a long time ago I've made a separate module - https://www.drupal.org/project/mimeinfo - which meets the needs. In case of 7.x it allows optionally override stream wrapper for checking the MIME type, 8.x - defines couple guessers from Symfony package and overrides addGuesser method of file.mime_type.guesser service to check that guesser supported (most part of last patch is there, in 8.x).

Status: Needs review » Needs work

The last submitted patch, 54: 2388749-54.patch, failed testing.

  • catch committed 4dae1d9 on 8.4.x
    Issue #2388749 by alexpott, adci_contributor, ParisLiakos: Register...
  • catch committed 5bf651d on 8.4.x
    Revert "Issue #2388749 by alexpott, adci_contributor, ParisLiakos:...

  • catch committed 4dae1d9 on 8.4.x
    Issue #2388749 by alexpott, adci_contributor, ParisLiakos: Register...
  • catch committed 5bf651d on 8.4.x
    Revert "Issue #2388749 by alexpott, adci_contributor, ParisLiakos:...

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

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now 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.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now 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.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now 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.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now 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.

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

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.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.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). 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.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now 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: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

PieterDC’s picture

Wow, this is an old issue.
But it got relevant to me today because I have to guess the mimetype of files that don't have extensions so I can judge whether that file is allowed or not.
Thanks for sharing your knowledge.

The patch from comment #54 still applies. I tried it on Drupal 8.9.x and it does what the issue description says.
But it does also break regular file upload functionality like with a file field on a node edit form.

I could cover my use case without that patch, but directly using the Symfony Guesser class and applying #3156672-6: ExtensionMimeTypeGuesser breaks other mime_type_guesser services to let the guesser based on file extension fall back to the other guessers.

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

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.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.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.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.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now 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.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now 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.

mxr576’s picture

Category: Task » Feature request
Issue tags: +jsonapi

Extending PieterDC's thoughts in #67

But it does also break regular file upload functionality like with a file field on a node edit form.

It also breaks file upload via JSONAPI - this is how I found this ancient one... - because also passes file name only here when \Symfony\Component\Mime\FileinfoMimeTypeGuesser::guessMimeType() needs a valid path. So the root cause of the issue is pretty much the same. (See also \Drupal\file\Upload\FileUploadHandler::handleFileUpload()

mxr576’s picture

Issue summary: View changes

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.