Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Написание новой политики для демона

Мы работаем с обычным демоном под Red Hat Enterprise Linux. Это значит, что есть его инициализирующий скрипт в /etc/init.d/ и им можно управлять используя chkconfig. К примеру, эта процедура подразумевает, что вы собираетесь использовать сервисную команду для управления запуском и остановкой демона.

По этой процедуре, вы пишете политику для пакета foo и ассоциированного с ним foo-демона.

Создайте файл $SELINUX_SRC/domains/program/foo.te.

Поместите макрос вызова демона в файл: daemon_domain(foo)

Создайте файл контекста файлов: $SELINUX_SRC/file_contexts/program/foo.fc.

Поместите первый список контекстов файлов в file.fc. Вам может понадобиться добавить кое-что к нему в дальнейшем, в зависимости от нужд демона

/usr/bin/foo -- system_u:object_r:foo_exec_t

/var/run/foo.pid --    system_u:object_r:foo_var_run_t

/etc/foo.conf  -- system_u:object_r:foo_conf_t

Загрузите новую политику, используя make load.

Пометьте foo-файлы: restorecon /usr/bin/foo /var/run/foo.pid /etc/foo.conf

Запустите демона, service foo start.

Просмотрите лог аудита в поисках сообщений об отказе:

grep "avc: denied" /var/log/messages > /tmp/avc_denials

cat /tmp/avc_denials

Посмотрите, что foo_t домен пытается создать сетевой сокет, это udp_socket или tcp_socket, как объект класса в отказе AVC.

avc: denied { create } for pid=7279 exe=/usr/bin/foo \

scontext=root:system_r:foo_t tcontext=root:system_r:foo_t\

tclass=udp_socket

В таком случае, добавьте макрос can_network() в foo.te: can_network(foo_t)

Продолжайте эти действия по базовым шагам, чтобы создать все правила, которые вы хотите. Каждый набор правил, добавленный к политике, может потребовать дополнительных разрешений для foo_t домена.

Запустите демона.

Прочтите AVC сообщения.

Составьте правило используя эти сообщения и свои знания, пытаясь по возможности использовать макрос.

Загрузите новую политику.

Вернитесь к началу – запустите демона.

Если демон пытается получить доступ к port_t, который связан с tclass=tcp_socket или tclass=udp_socket в логе АВЦ сообщений, вам необходимо определить, какой номер порта foo хочет использовать. Для диагностики (чтобы определить), поместите следующие правила в foo.te:

allow foo_t port_t:tcp_socket name_bind;

auditallow foo_t port_t:tcp_socket name_bind;

Продолжайте в том же духе по оставшимся AVC отказам. Когда они будут разрешены новой политикой, вы можете настроить уникальные требования для foo_t домена.

Запустив демона, определите, какой порт использует foo. Посмотрите на сообщение, разрешенное AVC и увидите, к какому порту подключен демон:

lsof | grep foo.*TCP

foo 2283 root 3u IPv6 3192 TCP *:4242 (LISTEN)

Foo-демон слушает порт 4242

Удалите общее правило port_t, заменив его специальным правилом для нового порта, основанным на домене foo_t.

type foo_port_t, port_type;

allow foo_t foo_port_t:tcp_socket name_bind;

Добавьте эту строку в $SELINUX_SRC/file_contexts. Это зарезервирует порт 4242 для домена foo_t:

ifdef(`foo.te', `portcon tcp 4242 system_u:object_r:foo_port_t')

 

Процессы в системе UNIX

Понятие и структура процесса

Процесс – это абстракция, применяемая в UNIX для описания выполняющейся программы. Это системный объект, посредством которого можно контролировать обращения программы к памяти, процессору и ресурсам ввода-вывода.

Компоненты процесса.

Процесс состоит из адресного пространства и набора структур данных, содержащихся внутри ядра. Адресное пространство представляет собой совокупность страниц памяти (базовые блоки размером, как правило, от 1 до 8 Кб), которые ядро выделило для выполнения процесса. В него загружается код процесса и используемые им библиотеки функций, переменные, стек и различная вспомогательная информация, необходимая ядру во время работы процесса. Поскольку в UNIX поддерживается концепция виртуальной памяти, страницы адресного пространства процесса в конкретный момент времени могут либо находиться в физической памяти целиком или частично, либо вообще отсутствовать там.

В структурах данных ядра хранится различная информация о каждом процессе. К наиболее важным относятся:

идентификационная информация о процессе;

статус процесса (неактивен, приостановлен, выполняется и т.п.);

информация для планировщика;

информация для организации межпроцессорного взаимодействия;

ссылки и связи процесса;

информация о времени исполнения и таймеры;

информация об используемых процессом ресурсах файловой системы;

информация о выделенном процессу адресном пространстве;

контекст процесса (информация о состоянии регистров процессора, стеке и т.д.)

Идентификатор процесса (PID).

Каждому новому процессу, созданному ядром, присваивается уникальный идентификатор (Process ID, PID). Большинство команд и системных вызовов, работающих с процессами, требуют указания конкретного идентификатора, чтобы был ясен контекст операции. Идентификационные номера присваиваются процессам по порядку по мере их создания. Когда номера заканчиваются, ядро сбрасывает счетчик в единицу и снова присваивает их по порядку, пропуская те идентификаторы, которые еще используются.

Идентификатор родительского процесса (PPID).

В UNIX нет системного вызова, который создавал бы новый процесс для выполнения конкретной программы. Вместо этого существующий процесс должен клонировать сам себя, чтобы породить новый процесс. Путь, по которому осуществляется клон, может отличаться от пути выполнения породившего его процесса.

Исходный процесс в терминологии UNIX называют родительским, а его клон – порожденным или дочерним. Помимо собственного идентификатора, каждый процесс имеет атрибут PPID (Parent Process ID), который равен идентификатору родительского процесса, породившего данный процесс.

Идентификатор пользователя (UID) и идентификатор группу (GID).

UID (User ID) – это идентификатор пользователя, создавшего данный процесс. Вносить изменения в процесс могут только его создатель (владелец) и пользователь root.

GID (Group ID) – это идентификатор группы, к которой относится владелец процесса.

Приоритет и значение nice.

От приоритета процесса зависит, какую долю времени ЦП он получит. Ядро применяет динамический алгоритм назначения приоритетов, учитывающий, сколько времени ЦП уже использовал процесс и сколько времени он ожидает своей очереди. Кроме того, учитывается заданный административным путем так называемый фактор уступчивости (устанавливается с помощью команды nice), определяющий, в какой степени программа может «делиться» процессором с другими программами. Чем выше значение nice, тем «уступчивее» программа.

Управляющий терминал.

Большинство процессов имеют связанный с ними управляющий терминал. Он определяет базовую конфигурацию стандартных каналов ввода, вывода и ошибок. Когда пользователь вводит какую-либо команду в интерпретаторе shell, его терминал, как правило, становится управляющим терминалом процесса. От управляющего терминала также зависит распределение сигналов.

 

Создание новых процессов

Новые процессы создаются в Linux методом «клонирования» какого-то уже существующего процесса, путем вызова системных функций clone() и fork(). Процедура порождения нового процесса выполняется в режиме ядра и происходит следующим образом.

Создается новая структура в таблице процессов ядра и содержание такой же структуры старого (или текущего) процесса копируется в новую структуру.

Назначается идентификатор (PID) нового процесса. PID – это уникальное положительное число, которое присваивается каждому процессу при его рождении. Именно по этим идентификаторам система различает процессы.

Увеличиваются счетчики открытия файлов (порожденный процесс наследует все открытые файлы родительского процесса).

После того, как процесс создан, запускается выполняемая им программа с помощью одного из вариантов системного вызова exec. Параметрами функции exec является имя выполняемого файла и, если нужно, параметры, которые будут переданы этой программе. Программа из указанного файла загружается в адресное пространство процесса, порожденного с помощью fork(), счетчик команд устанавливается в начальное значение и вновь созданный процесс переходит в режим ожидания того момента, когда планировщик выделит ему время центрального процессора.

В том процессе, откуда вызывались функции fork() и exec, управление передается в точку возврата из системного вызова и выполнение этого процесса продолжается. Родительский процесс может дожидаться окончания выполнения всех своих процессов-потомков с помощью системного вызова wait.

При чтении описания процедуры создания нового процесса может возникнуть вопрос: а зачем нужно копировать в новый процесс все данные процесса-родителя (например, код программы) и не слишком ли много времени займет копирование. Ответ на этот вопрос заключается в том, что при создании копии процесса его индивидуальные данные физически никуда не копируются. Вместо этого используется метод copy-on-write (копирование при записи): страницы данных обоих процессов особым образом помечаются, и только тогда, когда новый процесс пытается изменить содержимое какой-либо своей страницы, она дублируется.

 

Выполнение процесса

Процессы могут выполняться на переднем плане (foreground) – режим по умолчанию и в фоновом режиме (background). На переднем плане в каждый момент для текущего терминала может выполняться только один процесс. Однако пользователь может перейти в другой терминал и запустить на выполнение еще один процесс, а на другом терминале еще один и т.д. Процесс переднего плана – это процесс, с которым вы взаимодействуете, он получает информацию с клавиатуры (стандартный ввод) и посылает результаты на экран (стандартный вывод).

Фоновый процесс после своего запуска благодаря использованию специальной командой оболочки отключается от экрана и клавиатуры, т.е. не ожидает ввода данных со стандартного вывода и не выводит информацию на стандартный вывод, а командная оболочка не ожидает окончания запущенного процесса, что позволяет пользователю немедленно запустить еще один процесс.

Процессы так же могут быть отложенными. Отложенный процесс – это процесс, который в данный момент не выполняется и временно остановлен. После того как вы остановили процесс, в дальнейшем вы можете его продолжить как на переднем плане, так и в фоновом режиме. Возобновление приостановленного процессора не изменит его состояния – при возобновлении он начнется с того места, на котором был приостановлен.

Надо отметить, что запущенные процессы будут строго привязаны к конкретному терминалу и если пользователь отключится от него, то процессы не смогут продолжать свою работу и будут остановлены.

 

Демоны

Среди всех процессов можно выделить несколько особых типов процессов.

Системные процессы являются частью ядра и всегда находятся в оперативной памяти. Такие процессы не имеют соответствующих им программ в виде исполняемых файлов и запускаются особым образом при инициализации ядра системы. Примерами системных процессов являются планировщик процессов, диспетчер свопинга, диспетчер буферного кэша, диспетчер памяти ядра. Такие процессы являются фактически потоками ядра.

Демоны отличаются от обычных процессов только тем, что они работают в неинтерактивном режиме. Если с обычным процессом всегда ассоциирован какой-то терминал или псевдотерминал, через который осуществляется взаимодействие процесса с пользователем, то демон такого терминала не имеет. Демоны обычно используются для выполнения сервисных функций, обслуживания запросов от других процессов, причем не обязательно выполняющихся на данном компьютере. Пользователь не может непосредственно управлять демонами, он может влиять на их работу, только посылая им какие-то задания, например, отправляя документ на печать.

Одним из главных, если можно так выразиться, демонов в системе является демон init. Как уже говорилось, init является прародителем всех процессов в системе и имеет идентификатор 1. Выполнив задачи, поставленные в ему в файле inittab, демон init не завершает свою работу – он постоянно находится в памяти и отслеживает выполнение других процессов. Надо отметить, что init становится родителем «осиротевших» процессов (дочерних процессов, у которых родитель завершил свою работу), называемых зомби, для поддержания строгой иерархии процессов в системе.


Технологическая часть

Выбор дистрибутива

Дистрибутив – это определенный набор программ, утилит и документации, объединенный логичной системой установки и сопровождения программных пакетов, ориентированный на определенную группу пользователей и определенный тип задач.

По большому счету, обладая достаточными знаниями, можно накачать из Интернета ядро операционной системы, загрузчик, драйверы, программное обеспечение, и все это установить вручную, а потом долго подгонять и настраивать. Но все же проще взять готовый и настроенный дистрибутив.

На сегодняшний день существуют три базовых дистрибутива и множество их потомков, причем некоторые из них уже имеют крайне мало общего с родителями.

Вот эти три дистрибутива: Debian, Read Hat, Slackware.

Поддержка технология SELinux изначально заложена только в дистрибутивы группы Read Hat, поэтому была выбрана именно она.

Группа Read Hat включает в: Red Hat, Fedora Core, KSI, Black Cat, ASP Linux, AltLinux, Mandrake, BestLinux, TurboLinux и др.

Родителем всей этой группы является дистрибутив Red Hat. На сегодня это один из самых популярных дистрибутивов. Компания Red Hat представляет несколько вариантов поставки, но все они платные. Поэтому выбор пал на дистрибутив Fedora Core – настольную версию Red Hat, отправленной в свободное плавание.

Т.к. поддержка SELinux была включена в дистрибутивы Fedora Core только с версии 2, а последняя 5-ая версия считается еще не устоявшейся, то был выбран дистрибутив Fedora Core 4.


Создание демона

Демон – это консольное приложение, т.к. он работает в неинтерактивном режиме и графическая оболочка ему не нужна.

Создание демона можно логически разделить на три части:

Создание процесса с помощью fork();

Отрыв от управляющего терминала;

Обработка сигналов или событий (совершение так называемой полезной работы)

Системный вызов fork создает новый процесс. Возвращает идентификатор дочернего процесса или 0 в случае успеха и -1 в случае ошибки (код ошибки – в переменной errno).

На языке Си создание нового процесса будет выглядеть так:

int pid = fork ();

if (pid = = -1) // fork failed

{ perror (“Невозможно создать процесс!”);

exit (1);}

Else

{ if (pid <> 0) exit (0); // parent process exits}

Возвращаемое значение -1 свидетельствует об ошибке, но поскольку fork не имеет входных аргументов, то ошибочная ситуация никак не связана с вызывающим процессом. Единственная возможная ошибка – исчерпывание системных ресурсов, то есть либо нехватка места в файле подкачки, либо в системе исполняется слишком много процессов.

Т.к. по завершению fork оба процесса (потомок и предок) получают от него возвращаемое значение, а нам интересен потомок, то строкой «if(pid <> 0) then exit(0);» мы завершаем процесс предок (процесс-потомок получает от fork значение 0, родительский процесс – идентификатор процесса-потомка), в то время как дочерний процесс продолжает выполняться.

После завершения родительского процесса контроль над терминалом возвращается запустившей его программе (оболочке), а новый процесс, созданный функцией fork, выполняется в фоновом режиме. Однако наш процесс все еще принадлежит той же группе, что и создавший его процесс. Для того чтобы демон стал полностью независим от запустившего его терминала, новый процесс следует поместить в новую группу, а самый простой способ сделать это - создать новую сессию.

Новая сессия создается вызовом функции setsid:

pid = setsid ();

if (pid = = -1) // setsid failed

{ perror (“Невозможно создать новую сессию”);

exit (1);}

Теперь процесс выполняется в режиме демона.

Демон обычно несет на себе какие-либо полезные функции (например, демон диспетчера печати обрабатывает задания, отправленные на печатающее устройство), иначе его создание было бы лишено смысла.

В связи с этим было решено написать свой обработчик сигналов.

Сигналы – это запросы на прерывание на уровне процессов. В UNIX определено свыше тридцати различных сигналов. Когда поступает сигнал, возможен один из двух вариантов развития событий. Если процесс назначил данному сигналу подпрограмму обработки, то она вызывается, и ей предоставляется информация о контексте, в которой был сгенерирован сигнал. В противном случае ядро выполняет от имени процесса действия, определенные по умолчанию. Эти действия различны для разных сигналов.

Процесс вызова обработчика называют перехватом сигнала. Когда выполнение обработчика завершается, процесс возобновляется с той точки, где был получен сигнал.

Чтобы сигналы не поступали в программу, можно указать, что они должны игнорироваться или блокироваться. Игнорируемый сигнал просто пропускается и не оказывает на процесс никакого влияния. Блокируемый сигнал ставится в очередь на обработку, но ядро не требует от процесса никаких действий до явного разблокирования сигнала. Обработчик вызывается для разблокированного сигнала только один раз, даже если в течение периода блокировки поступило несколько таких сигналов.

В данной работе демон обрабатывает только сигналы SIGUSR1, SIGUSR2, SIGTERM, SIGINT, SIGQUIT. Остальные сигналы игнорируются, ну кроме сигнала SIGKILL (данный сигнал не может блокироваться и приводит к безусловному завершению процесса на уровне операционной системы).

Сигналы SIGUSR1 и SIGUSR2 не имеют стандартного назначения. Ими можно пользоваться в различных ситуациях.

При получении сигнала SIGUSR1 демон выводит на терминал информацию о программе, при получении SIGUSR2 – системную информацию(PID, PPID, UID, GID).

Все полученные сигналы протоколируется в файл с указанием времени получения сигнала.

 

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...