Socket race conditions
Простая задача: создать сокет, поправить у него права (например, для группового доступа) и передать соответствующей группе (например №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
Соответственно, сокет становится доступен только после последней операции.