How can mkdir() succeed but set wrong permissions?

Viewed 822

I have a problem with a piece of Linux code (written in C++) that does something like this:

  1. Create a new directory with mkdir("xyz",0755) which succeeds (return code is 0).
  2. Tries to open/create a new file in the justly created directory.
  3. Fails because the permissions on the new directory is actually 0600 and NOT 0755 as requested.

The code looks like this and checks that the path prefix "/tmp/slim" exists before coming to this part:

int somefunc(const string& path)
{
  if ( mkdir( path.c_str(), 0755 ) == 0 ) {
    // (*) if (!access( path.c_str(), F_OK | R_OK | W_OK | X_OK ) == 0 )
    // (*)   chmod( path.c_str(), 0755);
    string pidinfo = to_string( getpid() ) + "\n";
    string pidinfofile = path + "/pid";
    int fd = open( pidinfofile.c_str(), O_RDWR | O_CREAT, S_IWUSR | S_IRUSR );
    if ( fd == -1 )
      return 0;
    ssize_t written = write( fd, text.c_str(), text.size() );
    // ... do more stuff
  }
}

As this snippet of a strace (no lines missing/redacted) shows, the openat() fails even though the mkdir() returned 0.

13661 16:32:22.068465 mkdir("/tmp/slim/testsock", 0755) = 0
13661 16:32:22.068720 getpid()                = 13661
13661 16:32:22.068829 openat(AT_FDCWD, "/tmp/slim/testsock/pid", O_RDWR|O_CREAT, 0600) = -1 EACCES (Permission denied)

The result of running getfacl looks like this:

[localhost]$ getfacl /tmp/slim/
getfacl: Removing leading '/' from absolute path names
# file: tmp/slim/
# owner: stk
# group: stk
user::rwx
group::r-x
other::r-x

How can the the mkdir() return 0 but create a directory with permissions that differ from the specified? It's not a umask thing, I've tried setting umask to 0 before creating the directory without any effect. If the two commented lines marked with (*) is enabled/uncommented, things work as they should - but I don't like that sort of symptom treatment that skirts the real problem. There's got to be a reasonable explanation for this seemingly weird behavior.

Part of the story is that this works in an application with multiple threads. Each thread performs the code above (which is a smallish, thread safe function), and most threads succeed, but there's always 1 or 2 (out of 5-10) that fails as described.

1 Answers

Well, as it turned out (and quite expectedly, right?) it was a threading problem alright, as @RobertHarvey also hinted at. But I was also a bit right :-) when I wrote in a comment "there is some hidden, shared state somewhere". Well, the process umask is a shared, but perhaps not exactly hidden, state/variable. Here's what went wrong:

  1. One or more threads was executing the code above, happily creating directories.
  2. Simultaneously another thread was fiddling with the umask to ensure the right permission of a Unix socket it was creating, temporarily setting umask at 0177.
  3. Although the umask fiddling was ever so short, Murphy's Law dictated that the directories sometimes were created exactly while the umask was 0177, also forcing the directories to get permission mask 0600 rather than 0755.

Lesson (re-)learned: Watch out for hidden shared state/variables when using more than 1 thread.

Related