The upload module implements hook_file_download() in such a way that once it's enabled for any module to allow download the users must be granted 'view uploaded files' permission. The problem is that the module checks for permissions before checking that it's a file that it controls:

function upload_file_download($file) {
  if (!user_access('view uploaded files')) {
    return -1;
  }
  $file = file_create_path($file);
  $result = db_query("SELECT f.* FROM {files} f INNER JOIN {upload} u ON f.fid = u.fid WHERE filepath = '%s'", $file);
  if ($file = db_fetch_object($result)) {
    return array(
      'Content-Type: ' . $file->filemime,
      'Content-Length: ' . $file->filesize,
    );
  }
}

The attached patch moves the permissions check down so that it's only enforced once the file is known to be associated with an {upload} record.

To test this you'd need to:
- enable private file transfers
- grant one role 'view uploaded files' permissions
- upload files to a node
- determine the download URL of the uploaded file
- log in as the user with the 'view uploaded files' permissions and ensure that they're able to view the file
- log in as the user without the 'view uploaded files' permissions and ensure that they aren't able to view the file
-

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

drewish’s picture

Status: Active » Needs review
quicksketch’s picture

Big +1 here.

Upload module shouldn't be preventing the downloading of any file it doesn't control. As it is, upload is preventing access to all files (even not in the Edit: files upload table).

drewish’s picture

Over on #247780: No links on nodes I got a report from a user that this fixed their problem.

Hetta’s picture

Is this for 7.x-dev only, or would it work on 6.x or 5.x, too?
Thanks!

drewish’s picture

it will work on D6 as well, hopefully after we get it committed to D7 it can be backported to D6 as a bug fix.

Nda’s picture

Hi, this fixed the problem for me. I've noticed other iffyness with the upload permissions on another module some time ago but can't remember which one. Thanks drewish for pointing me to this.

quicksketch’s picture

Status: Needs review » Reviewed & tested by the community

Tested out and works great. The code change move is minor but critical for proper handling of files not uploaded through upload.module. This should be backported to 6.x. It probably won't be ported to 5.x though, because upload.module thinks it owns the files table in 5.x (in 6.x, upload.module was given it's own "upload" table).

webchick’s picture

floretan’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
1.45 KB
1.01 KB

This is definitely a bug. The documentation for hook_file_download is suffering from the same problem, and does not indicate that the hook should not return anything (or NULL) for files not controlled by the current module, so here's a patch to fix that (applies to the docs in contrib).

Also, $file is used both as a string and as an object inside of upload_file_download(), and although PHP allows it, it's bad practice to use the same variable for many purposes. Since we're already patching upload_file_download(), we might as well fix it.

drewish’s picture

Status: Needs review » Reviewed & tested by the community

flobruit I made some changes to your docs patch and went ahead and committed. I think you're also correct to use a different variable for the filepath.

Dries’s picture

Version: 7.x-dev » 6.x-dev

I've committed this to CVS HEAD. I'll leave it up to Gabor to commit to DRUPAL-6. I think it is safe to do, but some more eyeballs wouldn't hurt.

Gábor Hojtsy’s picture

Version: 6.x-dev » 7.x-dev
Status: Reviewed & tested by the community » Needs work

Looks like Dries was quick to commit this to Drupal 7. The function argument was renamed from $file to $filepath, which is a great thing, since $file is an object later in the same function. But the db_query() argument is $file which does not have any value at that time (it should also be $filepath as far as I see), or otherwise this will never work. Lets fix in D7 and then backport the right fix.

drewish’s picture

Status: Needs work » Needs review
FileSize
1 KB

-1 for my patch reviewing karma. here's the fix.

floretan’s picture

Status: Needs review » Reviewed & tested by the community

-1 for my variable renaming skills. The fix is good.

moshe weitzman’s picture

This is a simple bug fix. Lets commit.

Damien Tournoud’s picture

Title: upload.module hard-codes 'view uploaded files' permission check » Tests needed: upload.module hard-codes 'view uploaded files' permission check
Priority: Normal » Critical

Private downloads are currently broken in the 7.x branch because of this, and we have no tests to prove it.

Dries’s picture

Status: Reviewed & tested by the community » Needs work

I've just committed the bug fix but we still need tests for it.

webchick’s picture

Component: upload.module » tests

Moving to "tests" component for more visibility.

Damien Tournoud’s picture

@webchick: shouldn't we try to get this to 6 before (priority-wise)?

catch’s picture

Version: 7.x-dev » 6.x-dev
Component: tests » upload.module
Status: Needs work » Patch (to be ported)

Moving back to 6.x, looks like we don't have a committable patch for that branch yet.

catch’s picture

Title: Tests needed: upload.module hard-codes 'view uploaded files' permission check » Upload.module hard-codes 'view uploaded files' permission check

I've opened a separate issue for tests so we can concentrate on the Drupal 6 breakage here: #284269: Tests for private file transfers

drewish’s picture

Status: Patch (to be ported) » Reviewed & tested by the community
FileSize
1.73 KB

here's HEAD's patch for D6.

Gábor Hojtsy’s picture

Version: 6.x-dev » 7.x-dev
Status: Reviewed & tested by the community » Needs work

Did some research and found that people experienced the same bug in 2006 and it was fixed in the then current version (4.7.x): http://drupal.org/node/59648 (looks like it was not forward ported).

Anyway Drupal 6.4 was released with this fix in there, which seems like a better idea:

/**
 * Implementation of hook_file_download().
 */
function upload_file_download($filepath) {
  $filepath = file_create_path($filepath);
  $result = db_query("SELECT f.*, u.nid FROM {files} f INNER JOIN {upload} u ON f.fid = u.fid WHERE filepath = '%s'", $filepath);
  if ($file = db_fetch_object($result)) {
    if (user_access('view uploaded files') && ($node = node_load($file->nid)) && node_access('view', $node)) {
      return array(
        'Content-Type: ' . $file->filemime,
        'Content-Length: ' . $file->filesize,
      );
    }
    else {
      return -1;
    }
  }
}

Note that it does not only check whether the user has the 'view uploaded files' permission, it also checks, whether the user has access to the related node. There are other fixes released in the 6.4 security release, which could be missing from Drupal 7: http://cvs.drupal.org/viewvc.py/drupal/drupal/modules/upload/upload.modu...

BTW there is already an open issue for this node_access() check with the use case of assigning the upload to multiple nodes with different access checks: http://drupal.org/node/295586 so it is not the best end of all things.

drewish’s picture

Status: Needs work » Needs review
FileSize
818 bytes

here's a patch based on Gábor's code from D6.

Status: Needs review » Needs work

The last submitted patch failed testing.

quicksketch’s picture

We really, really need tests for this. #352236: Finish moving upload.module to DB:TNG broke the upload permissions again. I've created a new issue to fix the problem, #371206: upload_file_download() Blocks Access to Files Upload Doesn't Own.

quicksketch’s picture

Status: Needs work » Needs review
FileSize
4.41 KB

Hmm, seems like my patch linked in #26 is the same problem drewish fixes in #24. Either patch will work fine since they're identical in implementation.

Here's a suite for testing private downloads and upload module. Tests the following cases:
- Privileged user is allowed to view files uploaded through upload module.
- Privileged user is not prevented from viewing files not uploaded through upload module.
- Privileged user is not allowed to view an unmanaged file (since no module is available to set headers).
- Unprivileged user is not allowed to view files uploaded through upload module.

I've wrapped back in #371206: upload_file_download() Blocks Access to Files Upload Doesn't Own to make it so that the tests pass, since otherwise they wouldn't as upload.module currently has the bug reported in that issue.

I'll be happy to never see this problem again.

quicksketch’s picture

My test for checking "Access Denied" on unmanaged files was backward (I was checking for 200 instead of 404). This patch correctly tests unmanaged files and fixes a spacing issue.

Status: Needs review » Needs work

The last submitted patch failed testing.

c960657’s picture

Status: Needs work » Needs review
FileSize
7.56 KB

Updated patch. Also fixes two regressions introduced in #517814: File API Stream Wrapper Conversion (1: uploaded files were always made public, and 2: wrong variable name used in file_build_uri()).

drewish’s picture

these changes look really good but i'd like to make this query a bit more specific so it doesn't look like it's replicating file_load():

+  $file = db_query("SELECT f.*, u.nid FROM {file} f INNER JOIN {upload} u ON f.fid = u.fid WHERE uri = :uri", array(':uri' => $uri))->fetchObject();

to:

+  $file = db_query("SELECT f.filemime, f.filesize, u.nid FROM {file} f INNER JOIN {upload} u ON f.fid = u.fid WHERE uri = :uri", array(':uri' => $uri))->fetchObject();
c960657’s picture

Done.

c960657’s picture

Sorry, this is better.

c960657’s picture

And this is even better.

Status: Needs review » Needs work

The last submitted patch failed testing.

c960657’s picture

Status: Needs work » Needs review
FileSize
7.63 KB

Reroll.

Status: Needs review » Needs work

The last submitted patch failed testing.

c960657’s picture

Status: Needs work » Needs review

A testbot glitch?

roychri’s picture

Status: Needs review » Reviewed & tested by the community

This patch works for me.

I tried running the upload test in the patch without the actual changes and I get failures.
Running the upload test with the full patch runs without any problem.

Status: Reviewed & tested by the community » Needs work

The last submitted patch failed testing.

c960657’s picture

Status: Needs work » Needs review

The tests broke due to an accidental commit in #118345: Revamp hook_user_form/_register/_validate/_submit/_insert/_update (see #38).

Status: Needs review » Needs work

The last submitted patch failed testing.

c960657’s picture

Status: Needs work » Needs review
FileSize
7.56 KB

Reroll.

webchick’s picture

Version: 7.x-dev » 6.x-dev

I think I would rather fix this in 7.x with #563000: Provide upgrade path to file. So, assuming that patch makes it in, this is a 6.x-only bug.

sterwa requested that failed test be re-tested.

c960657’s picture

Status: Needs review » Fixed

The D6 issue was fixed in this commit:
http://drupalcode.org/viewvc/drupal/drupal/modules/upload/upload.module?...

There CVS commit message was simply “Drupal 6.4.”, i.e. there was no issue reference. The 6.4 release notes refers to SA-2008-047 that mentions “various Upload module vulnerabilities”.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.