Unix Domain Sockets: How to create a secure temp file name that doesn't exist as a file yet?

Thanks, but I wanted to avoid creating the file and setting permissions in two calls so there wasn’t a window where a bad actor could mess with things before the second call. Unsure what the exact risk would be here but trying to be safe.

I’d use tmp_file/2 to create the directory name as that has all the logic to find the right location. It isn’t safe, but that doesn’t matter. First we create the directory and then we use chmod/2 to set its permissions to 0700. If we succeed in that, we know the directory is ours (otherwise we couldn’t change the mode) and is fully protected. Someone could have used the two non-atomic steps to either create the directory before we did (but than we cannot change the mode) or enter the directory before we closed it. Neither is a problem as without access to the directory that process will not be able to access the socket you create inside it. If any of the steps fails, just try to remove what you created and try again.

OK, taking @jan’s feedback, let see how this version flies. I had concerns about adding retry logic if it fails. Because, if it fails, either:

  1. The tmp directory has permissions such that it will never work
  2. You have a bad actor trying to do bad stuff
  3. You are getting a legitimate conflict, which seems very unlikely given the naming scheme includes the process ID and a monotonically increasing counter and a base name.

So, it seems like the cause of failure is 1 or 2, which you’d want to know about (by failing, not retrying). Let me know if I’m missing something.

Code to delete is the same as above.

unix_domain_socket_path(Created_Directory, Absolute_File_Path) :-
    tmp_file(udsock, Created_Directory),
    make_directory(Created_Directory),
    catch(  chmod(Created_Directory, urwx),
            Exception,
            (   catch(delete_directory(Created_Directory), error(_, _), true),
                throw(Exception)
            )
    ),
    setup_call_cleanup( (   current_prolog_flag(tmp_dir, Save_Tmp_Dir),
                            set_prolog_flag(tmp_dir, Created_Directory)
                        ),
                        tmp_file_stream(Absolute_File_Path, Stream, []),
                        set_prolog_flag(tmp_dir, Save_Tmp_Dir)
                      ),
    close(Stream).

Thinking about it again there is a more elegant solution. Simply use a name for the directory based on the user name (which you can get through the environment or using library(uid)), say swipl-server-<user>. The tmp directory is available using current_prolog_flag/2 tmp_dir. If the directory is not present, create it and used chmod to make its mode 0700. If it is available, still make the mode 0700. This will raise an exception if you are not the owner of this file. Should this happen, just give up with an error. It shouldn’t happen unless you changed your user id or some malicious thing is happening on your system.

Now for anonymous servers create the socket based on the PID and for shared servers create it with a known name. All safe and simple :slight_smile:

You can also avoid /tmp and use the app data notion:

?- absolute_file_name(user_app_data(.), Dir, [file_type(directory),access(write)]).
Dir = '/home/janw/.local/share/swi-prolog'.

and create a directory with a known below it for your sockets. Still set the mode to 0700! In most cases the 92 char limit should be no problem.