2

I have a PHP script moving a file using function rename, which partly fails. Calling rename gives a "Permission denied" warning. The file seems to have been copied to the target directory (I see it there all right), but it is still present in the source directory after rename.

file_exists confirms that the old file is still present. unlink can then successfully delete the file – it returns true and file_exists confirms that the file is gone now.

The file comes from being uploaded in an HTTP request into /tmp directory (and I'm using is_uploaded_file to satisfy security considerations – this is not the issue here). The file does have rw permissions for the webservice user (www-data). move_uploaded_file works without errors, too.

The destination directory is in a mounted CIFS directory.

Linux Ubuntu, PHP version 7.2.24.

Jānis Elmeris
  • 1,975
  • 1
  • 25
  • 43
  • 1
    The file could have been `fopen`ed and not `fclose`d – Cid Jan 22 '20 at 12:37
  • I expect it's chown/chmod issues. What are the file ownerships and permissions of both the source and destination files, and their respective parent directories? (ls -la, if you're on *nix). – wally Jan 22 '20 at 12:40
  • @Cid, no nothing is being done with the file by the script other than moving, and nothing between trying to rename it and delete it. – Jānis Elmeris Jan 22 '20 at 12:43
  • @JānisElmeris can you show us the piece of code you are using to move the file ? (the `rename()` call should be enough) – Cid Jan 22 '20 at 12:45
  • Could be a [cache issue](https://stackoverflow.com/a/8548685/8398549) – Cid Jan 22 '20 at 12:48
  • @wally source directory is `drwxrwxrwt` (`/tmp`), file is `-rw-------` with the webserver as the owner, the destination directory is in a mounted CIFS directory – not sure how related would be its permissions. But the thing is that I do see the "renamed" file in there, so there are permissions enough for the webserver to create a file in there. – Jānis Elmeris Jan 22 '20 at 12:50
  • @Cid, I added `clearstatcache();` after `rename` and after `unlink`, and `file_exists` still reported the same results – the file was still there after `rename`, and it was not after `unlink`. If that's what you meant by caching. – Jānis Elmeris Jan 22 '20 at 12:55
  • CIFS mounted directory would further suggest a permissions issue. You can simulate what PHP is trying to do with `sudo`: `sudo -u www-data chmod 640 /target/file/path.txt`. How was the CIFS path mounted? Linux will still try to honour the (botched) CIFS permissions onto the local filesystem tree. – wally Jan 22 '20 at 13:00
  • Oh and show us the output of `mount` listing the CIFS mount options. – wally Jan 22 '20 at 13:03
  • @wally Yes, the simulation chmods all right but changing the ownership with `chown` is denied. – Jānis Elmeris Jan 22 '20 at 14:07
  • @wally Mount options would be `rw,relatime,vers=1.0,cache=strict,username=devv,uid=0,noforceuid,gid=0,noforcegid,addr=10.10.10.101,soft,unix,posixpaths,serverino,mapposix,acl,noperm,rsize=1048576,wsize=65536,bsize=1048576,echo_interval=60,actimeo=1` – Jānis Elmeris Jan 22 '20 at 14:09
  • `forceuid` might help. Unfortunately I’ve not got easy access to a box to test it right now. As per my comment on my answer - it looks like you’ve worked around the problem elegantly anyway! – wally Jan 22 '20 at 14:11

1 Answers1

2

(Updated: OP says the target directory is a CIFS mount. Permissions on CIFS mounts can be tricker to comprehend - see https://linux.die.net/man/8/mount.cifs section "File And Directory Ownership And Permissions")

rename isn't a single atomic action (in PHP). It actually calls (in this order for normal files):

  • copy (create a new copy of the file)
  • chown (set the ownership of the new copy)
  • chmod (set the permissions of the new copy)
  • unlink (delete the original)

(Source code is available on GitHub.)

If any of these steps fails, it aborts with an error code, and does not rollback (which would align with the symptoms you're seeing).

Side note: this is actually documented in the source code as possible behaviour:

 /*
  * Try to set user and permission info on the target.
  * If we're not root, then some of these may fail.
  * We try chown first, to set proper group info, relying
  * on the system environment to have proper umask to not allow                
  * access to the file in the meantime.
  */

I expect the permissions on the parent directory in the target (where you're copying to) are preventing the chmod or chown from working.

(NB: this will not work for a CIFS mount ... see link at the top of answer.) As a super ugly hack, you could try setting the permissions of the target directory to 777 (chmod 777 /my/target/directory/), which is bad practice, but would prove the theory.

wally
  • 3,492
  • 25
  • 31
  • Thank you for all the info and the pointer to the `rename` source! I decided to use `copy` + `unlink` instead of `rename` and it works (for my case anyway). `chmod` seems to work on CIF mounted files (we also checked the permissions on the origin server/filesystem, and they matched what we see in the locally mounted filesystem; not sure if that is odd or not), but `chown` fails. Chmodding the target dir to 777 (in the original filesystem) did not help. – Jānis Elmeris Jan 22 '20 at 14:04
  • So, does this mean PHP's `rename` may be used only by superusers? Isn't it too limited considering PHP scripts are mostly run by not-superusers, and renaming files seems a rather normal action. – Jānis Elmeris Jan 22 '20 at 14:05
  • I’m glad you’ve found a resolution! It means you may have issues if PHP isn’t running as super user. Which, is to be expected for almost anything filesystem related. Your only alternative is to find a way of allowing `www-data` chown permissions on the CIFS mount... reading the mount.cifs docs, this should work by default - but evidently isn’t. The `forceuid` mount option might help - but otherwise I think you’ve done the best thing by avoiding `rename` for this scenario. – wally Jan 22 '20 at 14:10