Socket race conditions
Материал из ALT Linux Wiki
Простая задача: создать сокет, поправить у него права (например, для группового доступа) и передать соответствующей группе (например №10).
Рассмотрим код, который ровно это реализует. Для того, чтобы убедиться в наличии небезопасных race conditions, добавим в него просмотр текущих прав на сокет.
#include <stdio.h> #include <sys/socket.h> #include <sys/un.h> #include <sys/stat.h> #define TESTSOCKET "/tmp/testsocket" void pstat(const char *path) { static int iter = 1; struct stat info; stat(path, &info); printf("%d) %4d:%-4d %o\n",iter++, info.st_uid, info.st_gid, info.st_mode); } void main(void) { struct sockaddr_un addr = { AF_UNIX, TESTSOCKET }; struct stat info; int sock = socket(AF_UNIX, SOCK_STREAM, 0); bind(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)); pstat(TESTSOCKET); chmod(TESTSOCKET, 0660); pstat(TESTSOCKET); chown(TESTSOCKET, -1, 10); pstat(TESTSOCKET); shutdown(sock, SHUT_RDWR); unlink(TESTSOCKET); }
компилируем, запускаем, наблюдаем следующее:
1) 500:500 140755 2) 500:500 140660 3) 500:10 140660
Это значит буквально вот что:
- После создания сокет получает права в соответствие с
umask
. В нашем случаеumask
не соответствует результату; хорошо ещё, что не 0 , а ведь бывает и так! В это время сокет доступен на чтение всем. - После
chmod()
сокет доступен на запись кому не надо: членам группы500
. В нашем случае это не страшно, но если бы запускающий процесс имел какую-нибудь более популярную группу в качестве основной, на это время сокет стал бы доступен на чтение-запись всем её членам. - После
chown()
наконец-то всё приходит в порядок.
Защита с помощью directory traversal
Есть два способа избежать небезопасных гонок. Самый простой — оставить в покое сам сокет и ограничивать права на каталог, в котором он заводится. В отличие от сокета, каталог можно завести заранее, выдать ему права, допустим "500:10 0750
". Тогда сокет, заведённый в этом каталоге, не будет доступен кому не надо в любом случае. Что, конечно, не отменяет chmod()
(да хоть 0666
).
Защита с помощью явного указания umask
Если по каким-то причинам перемещать сокет нельзя, необходимо сначала выставить umask
построже (например, 0777
), затем делать chown()
, и только затем — chmod()
.
- include <stdio.h>
- include <sys/socket.h>
- include <sys/un.h>
- include <sys/stat.h>
- define TESTSOCKET "/tmp/testsocket"
void pstat(const char *path) {
static int iter=1; struct stat info;
stat(path, &info); printf("%d) %4d:%-4d %o\n",iter++, info.st_uid, info.st_gid, info.st_mode);
}
void main(void) {
struct sockaddr_un addr = { AF_UNIX, TESTSOCKET }; struct stat info; int sock = socket(AF_UNIX, SOCK_STREAM, 0); mode_t oldumask = umask(0777);
bind(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)); umask(oldumask); pstat(TESTSOCKET); chown(TESTSOCKET, -1, 10); pstat(TESTSOCKET); chmod(TESTSOCKET, 0660); pstat(TESTSOCKET); shutdown(sock, SHUT_RDWR); unlink(TESTSOCKET);
}
Этот код работает так:
1) 500:500 140000 2) 500:10 140000 3) 500:10 140660
Соответственно, сокет становится доступен только после последней операции.