Как отсоединить исполняемый файл от процесса в Linux для обновления в режиме реального времени - PullRequest
0 голосов
/ 19 октября 2018

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

Есть ли возможность обойти / сломать эту блокировку?

Мой процесс выполняет mlockall(), поэтому все кодовые страницы уже загружены.

Цель состоит в том, чтобы процесс (долго выполняющаяся задача) обновлялся сам с минимальным временем простоя.

После загрузки execl(argv[0],NULL) должен активировать обновленный код.

Ответы [ 2 ]

0 голосов
/ 20 октября 2018

Обычно исполняемый файл не может быть перезаписан, пока процесс запускается из этого файла.Есть ли возможность обойти / сломать этот замок?

Да.Не перезаписывайте исполняемый файл;замените его.

То есть вы сохраняете новый исполняемый файл под временным именем в том же каталоге (или в любом месте той же файловой системы - должен быть на том же монтируемом!), а затем либо rename() или link() временный файл поверх исполняемого файла.

В сценарии оболочки вы можете использовать mv -f newbinary oldbinary, если оба значения newbinary и oldbinaryв той же файловой системе и смонтировать.В сценарии Bash вы можете использовать что-то вроде

#!/bin/bash
BINDIR=/usr/bin

# Autoremoved work directory
Work="$(mktemp -d)" || exit 1
trap "cd / ; rm -rf '$Work'" EXIT

# ... Check if new binaries available ...
#     Otherwise: exit 0

# ... Download new binaries under "$Work/" ...

# Copy 'executable' to $BINDIR, under a temporary name
tempbin="executable.$PID-$RANDOM$RANDOM$RANDOM"
if ! mv -f "$Work/executable" "$BINDIR/$tempbin" ; then
    # Failed
    exit 1
elif ! mv -f "$BINDIR/$tempbin" "$BINDIR/executable" ; then
    # Failed
    exit 1
fi

# Successfully replced.
exit 0

. Это работает во всех системах POSIXy, поскольку имя файла полностью отделено от inode , который определяет его содержимое, режим доступа, владельца., временные метки и т. д.

На практике ядро ​​будет сохранять старый inode до тех пор, пока есть исполняемые файлы, на которых он выполняется, или если какой-либо процесс его откроет.Однако имя файла сразу будет указывать на новый индекс с новым исполняемым содержимым.Таким образом, по сути, переименование / ссылка просто изменяет то, на какой индекс ссылается имя файла.Именно поэтому временный файл должен находиться в той же файловой системе (с тем же монтированием).

Цель - процесс (долго выполняющаяся задача) должен обновляться с минимальным временем простоя.

Это обычная дыра в безопасности, позволяющая процессу изменить себя.Обычно это даже не разрешается в системах POSIXy, если только процесс не выполняется с привилегиями суперпользователя (например, как root или в Linux с возможностью CAP_FOWNER).Вы НЕ хотите этого делать.

(Только потому, что это обычно делается, например, с веб-материалами PHP, это не делает его нормальным или безопасным. Если это так, то мы должны согласитьсяэтот экскремент имеет приятный вкус, потому что есть миллиарды мух и навозных жуков, которые так думают. Если вы посмотрите, вы обнаружите, что у таких веб-сервисов ALL были серьезные проблемы с безопасностью, некоторые из которых непосредственно связаны с этиммеханизм обновления. Некоторые сопровождающие упомянутого пакета утверждают, что проблемы во время обновлений, такие как атаки типа «человек посередине», являются ошибкой пользователей, а не их. Конечно, они ошибаются.)

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

Если ваши пользователи действительно этого хотят, вы можете создать минимальный демон C, который периодически проверяет, доступна ли новая версия.Вы можете получать его по определенному адресу дейтаграммы домена Unix, чтобы ваш исполняемый файл мог отправить ему один символ (независимо от того, от какого пользователя он запускается), чтобы демон обновления тут же проверил (если он не проверил)достаточно недавно).По сути, он просто будет ждать (скажем, используя select()) достаточно времени, чтобы пройти, или конкретного запроса для проверки.Когда придет время, он запустит сценарий оболочки, чтобы проверить, доступен ли новый исполняемый файл (скажем, с использованием popen() и т. Д .; типичное расположение для сохранения таких сценариев - /usr/lib/yourservice/).Если скрипт отвечает, что доступна новая версия, запустите другой скрипт, чтобы загрузить и заменить двоичный файл.Если процесс получает сигнал SIGHUP, выполните проверку немедленно;если он получает сигнал SIGTERM, выйдите.Таким образом, он может быть запущен как служба и не потреблять много ресурсов при запуске.

В вашем долговременном исполняемом файле, если он находится в точке, где он может заменить себя более новой версией, используйтеstat() на /proc/self/exe и argv[0], чтобы проверить, имеют ли они одинаковые st_dev и st_ino.Если они этого не делают, то служба обновлений предоставила более новую версию исполняемого файла, и ваша служба может запустить

    if (argv[0][0] == '/')
        execv(argv[0], argv);
    else
        execvp(argv[0], argv);

или, если вы определите абсолютный путь к вашему исполняемому файлу во время компиляции, скажем, exepath тогда

    execvp(exepath, argv);

, чтобы заменить себя более новой версией.

Обратите внимание, что такой процесс должен закрыть все открытые дескрипторы файлов (кроме стандартных потоков; 0, 1 и 2 или STDIN_FILENO, STDOUT_FILENOи STDERR_FILENO), когда он запускается.(То есть закройте все дескрипторы открытых файлов от 3 до sysconf(_SC_OPEN_MAX) включительно.) Это связано с тем, что функции exec*() не закрывают дескрипторы файлов (кроме отмеченных O_CLOEXEC / FD_CLOEXEC), поэтому любые дескрипторы, которые могутбыть открытым во время exec будет оставлено открытым.Это также означает, что в случае сбоя exec ваша служба может продолжить работу в обычном режиме.

0 голосов
/ 19 октября 2018

Для меня вы ошибаетесь в нескольких моментах:

  • сначала вы можете перезаписать двоичный файл, работающий в Linux (точнее, двоичный файл, файл которого хранится в ext {2,3,4} файловая система);Причина проста: до тех пор, пока в файле по-прежнему открыт дескриптор файла, иноды, связанные с этим файлом, остаются «выделенными» драйвером до тех пор, пока последний дескриптор файла не будет закрыт на нем, а затем блоки не будут освобождены.Таким образом, нет риска найти данные файла.
  • Код отображается в памяти при запуске процесса, поэтому не может быть пропущен код (и так как mmap использует дескриптор файла, даже в случае ленивого отображения,весь файл остается «сопоставляемым».
  • mlockall используется для блокировки страниц в памяти , что предотвращает обмен, это не имеет ничего общего с блокировкой файла в файловой системе.

В конце концов, ничто не мешает вам делать то, что вы просили.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...