connect() race condition for Unix domain sockets

Problem: connect() on Unix domain sockets follows symlinks, and there is no way to switch this off.

FsObjReal calls lstat() to determine whether a directory entry is a symlink. If not, real_file_socket_connect() can be used, and this will call connect(). Between lstat() and connect(), another process might replace the socket with a symlink.

Note: this problem does not apply to bind(), which does not follow symlinks. bind() behaves like open() with O_CREAT|O_EXCL. (However, there may be some other Unix variants where this is not true, perhaps including old versions of Linux.)

Note that Plash does not restrict access to abstract-namespace Unix domain sockets at all.

Applicability

An adversary, A, in one sandbox cannot exploit this on its own; it requires a conspirator, B. A and B can conspire so that A gets access to an arbitrary socket S that is in A's server's namespace but not in A's namespace. A and B must have write access to some common directory. B does not have to have access to socket S (it only needs to know its pathname in A's namespace). This exploit can only occur if B is not in the same sandbox as A.

Test case

See tests/socket-symlink-race

Possible solutions

Modify kernel

Add a new system call, either lconnect() or fconnectat(). The latter would take a flags argument. fconnectat() is preferable.

Without kernel changes

Hardlink the socket into a temporary directory and call connect() on it there.

The ServerProcess can have a private directory in /tmp in which no other ServerProcess will create symlinks on behalf of a client. The server's real_file_socket_connect() method hardlinks the socket file into the private directory, checks that the object that got hardlinked is not a symlink, and then calls connect() on it.

Problem: link() does not work across devices. Furthermore, link() does not work between directories that are on the same device but have been mounted with "mount --bind".

We could have a list of directories into which we try to create hard links. These are tried in turn. The user/administrator can set these up to cover all the filesystems.

Plash would have to create a directory per user ID inside these directories (as in /tmp). (This means setting the sticky bit isn't necessary, because processes with other user IDs can't delete the directories while they're in use.) What should the default be? "/tmp/plash/" This would be enough for opening X11 sockets. How should this be configured? Via an environment variable? We would want to unset this so that programs run under Plash don't see it. Via a configuration file in /etc? /etc/plash/hardlink-dirs could contain a list of directories, each on a separate line.

PlashIssues/ConnectRaceCondition (last edited 2008-08-17 11:54:00 by MarkSeaborn)