Демоны Ада

Часть 1

В этой статье речь пойдет о том, как из программы, написанной на языке Ада, сделать демона Linux. В первой части мы рассмотрим основы демонизации процесса. По сути, механизм стандартный для всех языков, однако при использовании компилятора GNAT и его RunTime библиотеки всплывает небольшая проблема, из‐за которой этот механизм придется допилить (хотя правильней было бы сказать подпереть костылями).

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

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

Проблема заключается в том, что после создания дочернего процесса через вызов fork, не получится запустить обработчики прерываний путем навешивания pragma Attach_Handler на защищенные процедуры. Не знаю почему, но пока придется смириться с тем, что GNAT RunTime не дружит с этим. Возможно здесь существуют и другие засады, да и демон, не умеющий обрабатывать системные прерывания — не полноценный демон. В любом случае нам придется в конечном итоге что‐то придумывать, но об этом в следующий раз.

Итак, попробуем «форкнуться» из под Ада программы:


          

          
pid : int;

function Fork return int;
pragma Import (C, Fork, "fork");

pid := Fork;
if pid < 0
   then OS_Exit (-1);
elsif pid > 0 then
   OS_Exit (0);
end if;

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

Системный вызов fork создаст копию родительского процесса, причем выполнение процесса‐потомка начнется сразу же с этого места (в нем fork вернет значение 0).

Таким образом, мы отделились от родительского процесса.

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


          

          
procedure Umask (Mask : int);
pragma Import (C, Umask, "umask");

Umask (0);

Теперь необходимо получить уникальный Session ID (SID) от ядра путем вызова setsid:


          

          
function SetSid return int;
pragma Import (C, SetSid, "setsid");

if SetSid < 0 then
   OS_Exit (-1);
end if;

Основная идея заключается в том, чтобы отвязаться от так называемого управляющего терминала. В случае наличия уязвимости, у атакующего появляется возможность получить доступ к управляющему терминалу (/dev/tty) и выполнить произвольный код. Учитывая, что демоны обычно запускаются с правами рута, натворить атакующий сможет все что угодно (даже если демон выполнил setuid на непривилегированного пользователя). В некоторых Unix системах недостаточного одного вызова setsid, но к Linux это не относится.

Во всех Unix и *nix-like системах гарантированно присутствует только одно место на файловой системе, а именно корень „/“. Поэтому неплохо было бы сменить текущий рабочий каталог на него:


          

          
function Chdir (New_Directory : char_array) return int;
pragma Import (C, Chdir, "chdir");

if Chdir (To_C ("/")) < 0 then
   OS_Exit (-1);
end if;

Остался последний штрих — закрыть стандартные файловые дескрипторы, ибо они излишни ввиду невозможности использования демоном терминала и могут создать угрозу безопасности:


          

          
Close (Standin);
Close (Standout);
Close (Standerr);

После этого можно начинать основной рабочий код демона.

Соберем все воедино:


          

          
-- File: daemon.adb

with Gnat.OS_Lib;
with Interfaces.C;

use Gnat.OS_Lib;
use Interfaces.C;

procedure Daemon is

   pid : int;

   function Fork return int;
   pragma Import (C, Fork, "fork");

   procedure Umask (Mask : int);
   pragma Import (C, Umask, "umask");

   function SetSid return int;
   pragma Import (C, SetSid, "setsid");

   function Chdir (New_Directory : char_array) return int;
   pragma Import (C, Chdir, "chdir");

begin

   pid := Fork;
   
   if pid < 0 then
      OS_Exit (-1);
   elsif pid > 0 then
      OS_Exit (0);
   end if;

   Umask (0);
   
   if SetSid < 0 then
      OS_Exit (-1);
   end if;
   
   if Chdir (To_C ("/")) < 0 then
      OS_Exit (-1);
   end if;

   Close (Standin);
   Close (Standout);
   Close (Standerr);

   loop
      delay 10.0; --  рабочий код :)
   end loop;
   
end Daemon;

Собираем:

$ gnatmake daemon

и запускаем

$ ./daemon

Смотрим вывод ps -ef

user 9147 1 0 16:11 ? 00:00:00 ./daemon

Видно, что демон успешно усыновился init'ом (pid 1) и не имеет управляющего терминала (знак ?). На этом закончим первую часть


(c) Gavrikov Valeriy ([email protected]), April 2012

Автор: Валерий Гавриков
Дата: 12.04.2012