Node: fs.copyFile on Mac OS does not respect permissions

Created on 26 Mar 2019  路  11Comments  路  Source: nodejs/node

  • Version: v11.2.0
  • Platform: Darwin iMac.local 18.2.0 Darwin Kernel Version 18.2.0: Thu Dec 20 20:46:53 PST 2018; root:xnu-4903.241.1~1/RELEASE_X86_64 x86_64
  • Subsystem:

When setting a file mode to 444 using chmodSync, then trying to copy another file over it, on Windows and on Linux, copyFile will throw an error, but on a Mac it does not, and it will override the file

fs.chmodSync(dest, '444');
// then try to 
fs.copyFile(source, dest, function (err) {
    //  i do expect an err here, 
   // but there is no error on Mac
});

i even check the stat.mode using parseInt(fs.statSync(dest).mode.toString(8), 10) and it does in fact say 100444

This is a sample when everything is working well
https://repl.it/repls/CylindricalGlisteningCondition

However, when i run it on my mac, I get this

This platform is darwin
fs.stat.mode ./dest: 100444
/Users/akhoury/rcloned/code/NodeBB/rrr.js:26
                throw new Error(`This should've thrown an error!`);
                ^

Error: This should've thrown an error!

Thanks

confirmed-bug fs libuv macos

Most helpful comment

libuv's fs.c has Apple-specific code for uv__fs_copyfile() added by @cjihrig in https://github.com/libuv/libuv/pull/1465. Do you know if there was a reason other than "use analogous native functionality when you can" for using different code for macOS? Removing it all and using the code used by other UNIX-like operating systems fixes this problem. node continues to pass all tests too. So maybe that's the way to go?

All 11 comments

I'm able to replicate this bug on Mojave. Seems like libuv is somehow running copyfile() with COPYFILE_UNLINK set or else there's a bug in macOS? @nodejs/fs @nodejs/libuv @nodejs/platform-macos

Confirmed with Node.js 8.x, 10.x, 11.x, and current master branch. (fs.copyFile() does not exist in Node.js 6.x.)

I tried adding this to libuv's fs.c file and recompiling but it didn't change anything...

  // Turn off unlinking of the destination file.
  // Refs: https://github.com/nodejs/node/issues/26936
  flags &= ~(1 << 21); /* COPYFILE_UNLINK */

(Of course, it's entirely likely I'm doing the bit math wrong or something simple like that.....)

I'm able to replicate this bug on Mojave. Seems like libuv is somehow running copyfile() with COPYFILE_UNLINK set or else there's a bug in macOS? @nodejs/fs @nodejs/libuv @nodejs/platform-macos

I'm not a macOS person, but being able to bypass file permissions sounds like an OS bug.

This seems to be related to permissions and probably not a bug we can control. I'm also seeing no error by default, but if I run with sudo -u nobody then I get an EPERM error.

I'm not a macOS person, but being able to bypass file permissions sounds like an OS bug.

Yeah, definitely agree after playing around with different copyfile() flags inside libuv. Astonishingly, it seems that copyfile(3) on macOS does not respect file permissions. We can't be the first people to ever experience this, though... 馃

libuv's fs.c has Apple-specific code for uv__fs_copyfile() added by @cjihrig in https://github.com/libuv/libuv/pull/1465. Do you know if there was a reason other than "use analogous native functionality when you can" for using different code for macOS? Removing it all and using the code used by other UNIX-like operating systems fixes this problem. node continues to pass all tests too. So maybe that's the way to go?

I had recently noticed this behavior divergence also, as it can occur in the context of symlinks (hard or soft): https://github.com/libuv/libuv/issues/2199#issuecomment-466134438. Turning that into an issue has been on my TODO list (and providing analysis on what it seems like it should do), but I think this should already address it.

I was actually a bit surprised by that the proposed behavior here is the desired choice, since it means that a copy may end up with different attributes from the original file, and it uses either the permissions of the hardlink or of the containing directory, if the destination didn't exist at the time. The result is not necessarily more nor less restrictive, just different.

But it seems that what libuv does on unix is the explicit choice for the C++17 standard for std::filesystem::copy_file and is also what unix cp appears to do. So that seems like a good track record to follow. By contrast, cp -f behaves either like "unix" (if the dest file could be opened for write) or like "macos" (if the dest file could not).

Shell test script:

echo 1 > file1
echo 2 > file2
ln file1 file_link
# optional: chmod a-w file1
cp file2 file_link # or cp -f
cat file1 # prints 2
Was this page helpful?
0 / 5 - 0 ratings