Can't access a private file on a NFS mount when its UID and GID do not exist on the host system

Created on 23 March 2022, over 2 years ago
Updated 15 April 2024, 7 months ago

Problem/Motivation

On one of our websites, $settings['file_private_path'] points to a folder on an NFS mount.
Trying to download an image fails with this exception:

Got error 'PHP message: Uncaught PHP Exception Symfony\\Component\\HttpFoundation\\File\\Exception\\FileException: "File must be readable." at /home/insite/staging/sites/insite-losange-master/vendor/symfony/http-foundation/BinaryFileResponse.php line 97'

This happens because BinaryFileResponse::setFile() calls File::isReadable() on the private:// URI, then PHP calls PrivateStream::url_stat() and tries to decide if it can read the file.
This does not work because PrivateStream::url_stat() returns an UID and GID specific to NFS, that does not exist on the host system.

We can also reproduce this by comparing these calls that both point to the same file:

// Returns FALSE.
is_readable('private://foo.jpg');

// Returns TRUE.
is_readable('/mnt/nfs/foo.jpg');

Internally, when is_readable() or is_writable() is called on a path, PHP uses the access() system call to check access. This works for NFS because the system knows how to translate NFS permissions.
But when called on a stream wrapper, PHP uses the information returned by url_stat() to know if it has access to the file.
This does not work if the NFS user is different from the PHP user.

Steps to reproduce

Proposed resolution

As a workaround, we extended PrivateStream like this:

<?php

namespace Drupal\foo\StreamWrapper;

use Drupal\Core\StreamWrapper\PrivateStream;

class NfsPrivateStream extends PrivateStream {

  /**
   * @param $uri
   * @param $flags
   *
   * @return array|false
   * @link https://github.com/aws/aws-sdk-php/blob/93d6049a3a412818cf3df379b662d2e0b75252d0/src/S3/StreamWrapper.php#L775
   */
  public function url_stat($uri, $flags) {
    $stat = parent::url_stat($uri, $flags);

    $realpath = $this->getLocalPath($uri);

    if (is_writable($realpath)) {
      if (is_file($realpath)) {
        $stat['mode'] = $stat[2] = 0100777;
      }
      elseif (is_dir($realpath)) {
        $stat['mode'] = $stat[2] = 0040777;
      }
    }

    return $stat;
  }

}

We call is_writable() on the full path (so the real access is checked by the system) and if true we lie to PHP by saying the file is fully writable and readable (777 mode).
This works for our specific use case, because we know that if PHP can write to the NFS path, it will be able to also read it.
A more general solution would probably be more complex.

🐛 Bug report
Status

Closed: won't fix

Version

11.0 🔥

Component
File system 

Last updated about 11 hours ago

Created by

🇫🇷France prudloff Lille

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇳🇿New Zealand luke.stewart

    Given comment 2 by @longwave points out this is limited to some NFS installs with specific config and suggests the solution is best used in contrib, and that there has been no response on this issue since then for 2 years.

    I had a quick search of documentation to see if there were any NFS specific pages that we might want to add some information and potentially link to this issue however I didn't spot anything relevant.

    Given the above I'm going to mark this as closed (won't fix).

    Feel free to reopen if you feel this isn't warranted.

Production build 0.71.5 2024