Программа, занимающаяся системной безопасностью, не должна надеяться на монопольный доступ к содержимому файла. Более точно, важно должным образом учитывать риск возникновения условий перехвата при работе с одним файлом. Основная опасность исходит от пользователя, одновременно запускающего несколько копий Set-UID root приложения или одновременно устанавливающего несколько соединений с одним демоном, надеясь создать условие перехвата, во время которого содержимое системного файла может быть модифицировано нестандартным образом.
Чтобы программа не была восприимчива к такого рода ситуациям, необходимо ввести механизм монопольного доступа к файлу данных. Это та же проблема, которая возникает в базах данных, когда различным пользователям позволено одновременно делать запрос и изменять содержимое файла. Принцип блокировки файла решает эту проблему.
Когда процесс желает произвести запись в файл, он требует у ядра заблокировать этот файл или часть его. Пока процесс держит блокировку, ни один другой процесс не может запросить блокировку этого файла или, как минимум, той же части файла. Подобным образом, процесс требует блокировки перед чтением содержимого файла, чтобы быть уверенным, что никакие изменения не будут внесены, пока он держит блокировку.
На самом деле, система умнее, чем мы описали: ядро различает блокировки, требуемые для чтения и для записи. Различные процессы могут одновременно держать блокировку для чтения, так как ни один из них не попытается изменить содержимое файла. Однако, в данный промежуток времени только один процесс может держать блокировку для записи, и в это время не могут быть предоставлены никакие другие блокировки, даже по чтению.
Существует два типа блокировок (по большей части друг с другом не совместимых). Первый, из BSD, основан на системном вызове flock(). Первый аргумент вызова - дескриптор файла, к которому вы желаете получить монопольный доступ, второй - именованая константа, задающая операцию, которая будет производиться. Она может принимать различные значения: LOCK_SH (блокировка для чтения), LOCK_EX (для записи), LOCK_UN (снятие блокировки). Система блокирует процесс на время, пока запрошенная операция невозможна. Однако, вы можете произвести операцию ИЛИ | второго аргумента с константой LOCK_NB, чтобы функция неудачно завершалась вместо того, чтобы приостанавливаться.
Второй тип блокировки, из System V, основан на системном вызове fcntl(), немного сложном. Существует библиотечная функция lockf() близкая к системному вызову, но не до конца. Первый аргумент fcntl() - дескриптор файла для блокировки. Второй - представляет необходимую операцию: F_SETLK и F_SETLKW управляют блокировками, вторая команда приостанавливает процесс пока операция не становится доступной, в то время как первая сразу же возвращает управление при неудачной попытке. F_GETLK проверяет состояние блокировки файла (что бесполезно для рассматриваемых приложений). Третий аргумент - указатель на переменную типа struct flock, которая описывает блокировку. Важные поля структуры flock следующие:
Имя
Тип
Значение
l_type
int
Ожидаемое действие: F_RDLCK (блокировка для чтения),
F_WRLCK (для записи) and F_UNLCK (для снятия блокировки).
l_whence
int
Тип смещения поля l_start (обычно
SEEK_SET).
l_start
off_t
Позичия начала блокировки (обычно 0).
l_len
off_t
Длина блокировки, 0 для достижения конца файла.
Мы можем видеть, что fcntl() способна блокировать ограниченные части файла, она может делать много чего по сравнению с flock(). Давайте взглянем на программу, которая запрашивает блокировку для чтения для файлов, имена которых указаны как аргументы, и ожидает пока пользователь нажмет Ввод перед выходом (и снятием блокировок).
Вначале мы запускаем эту программу с первой консоли, где она будет ожидать:
$ cc -Wall ex_03.c -o ex_03
$ ./ex_03 myfile
Нажмите Ввод для снятия блокировки(ок)
>С другого терминала...
$ ./ex_03 myfile
Невозможно заблокировать myfile
$
Нажимая Ввод на первой консоли, мы снимаем блокировки.
При помощи механизма блокировок, вы можете избежать появления условий перехвата в директориях и очередях печати, как демон lpd, который использует flock() для блокировки файла /var/lock/subsys/lpd, таким образом допуская только один экземпляр для выполнения. Вы также можете безопасно управлять доступом к системным файлам, также как в библиотеке pam файл /etc/passwd блокируется при помощи fcntl(), при изменении данных пользователя.
Однако, такие действия защитят вас только от вмешательства корректно работающих приложений, то есть приложений, которые запрашивают у ядра разрешения на требуемый доступ, перед чтением или записью в важный системный файл. То есть мы говорим о совместном блокировании - такие приложения ответственно относятся к досупу к данным. К сожалению, плохо написаная программа может изменить содержимое файла, даже если другой процесс, с правильным поведением, заблокировал его для записи. Вот пример. Мы записываем несколько букв в файл и блокируем его, используя предыдущую программу:
Возвращаясь на первую консоль, мы проверяем "повреждения":
(Ввод)
$ cat myfile
SECOND
$
Чтобы решить эту проблему, ядро Linux предоставляет системному администратору механизм блокирования из System V. Поэтому вы можете использовать этот механизм только при помощи fcntl(), но не flock(). Администратор может сказать ядру, что блокировки fcntl()строгие, использующие специальную комбинацию прав доступа. Тогда, если процесс блокирует файл для записи, другой процесс не сможет изменить этот файл (даже root). Специальная комбинация прав, которая используется, - это установленный бит Set-GID при снятом бите выполнения для группы. Этого можно добиться командой:
$ chmod g+s-x myfile
$
Однако этого не достаточно. Чтобы автоматически пользоваться строгими совместными блокировками при обращении к файлу, должен быть установлен атрибут mandatory для раздела, где находится этот файл. Обычно, вам надо изменить файл /etc/fstab и добавить туда опцию mand в 4-ую колонку или ввести команду:
# mount
/dev/hda5 on / type ext2 (rw)
[...]
# mount / -o remount,mand
# mount
/dev/hda5 on / type ext2 (rw,mand)
[...]
#
Теперь мы можем проверить, что изменение с другой консоли невозможно:
$ ./ex_03 myfile
Нажмите Ввод для снятия блокировки(ок)
>С другого терминала:
$ echo "THIRD" > myfile
bash: myfile: Resource temporarily not available
$
И возвращаясь на первую консоль:
(Ввод)
$ cat myfile
SECOND
$
Администратору-не программисту следует принять решение об использовании строгих блокировок файлов (например /etc/passwd или /etc/shadow). Программисту необходимо котролировать способы доступа к данным, что обеспечит его приложению при чтении правильную работу с данными, а при записи оно не создаст помех другим процессам, если будет правильно настроено окружение для выполнения.