Stdbuf с setuid/возможностями

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

Пока stdbuf работает отлично, однако процесс использует SOCK_RAW и должен либо запускаться как root, либо иметь setuid (0) или cap_net_raw. При запуске как не-root с setuid или возможностями stdbuf, похоже, игнорируется. Позвольте мне продемонстрировать проблему:

Это простой писатель:

#include <stdio.h>
#include <unistd.h>

int main(){
        int i;
        for ( i = 0;; i++){
                fprintf(stdout, "%d\n", i);
                sleep(1);
        }
}

И простой читатель:

#include <stdio.h>

int main(){
        char* line = NULL;
        size_t n = 0;
        while (getline(&line, &n, stdin) != -1 ) {
                fputs(line, stdout);
        }
}

Как и ожидалось, при выполнении ./writer | ./reader ничего не отображается до заполнения буфера. Prepending stdbuf -oL позволяет буферизацию строк, и я получаю строки в считывателе:

% stdbuf -oL ./writer | ./reader
0
1
2
...

Но если я добавлю cap_net_raw+ep, он перестанет работать:

% sudo setcap cap_net_raw+ep ./writer
% stdbuf -oL ./writer | ./reader
(no output)

Такое же поведение наблюдается при использовании setuid:

% sudo chown root:root ./writer
% sudo chmod +s ./writer
% stdbuf -oL ./writer | ./reader
(no output)

Мне интересно понять, почему это происходит, и как я могу продолжать использовать stdbuf без запуска от root. Я признаю, что я не совсем понимаю, что делает setuid за кулисами.

Ответ 1

От взгляда на исходный код stdbuf похоже, что он работает, установив LD_PRELOAD. Конечно, проблемы безопасности связаны с LD_PRELOAD с исполняемыми файлами setuid или sudo.

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

Другим, более простым вариантом будет избежать stdbuf и просто вызвать fflush(stdout) из исходного кода.

Ответ 2

Решение без LD_PRELOAD

Вы можете использовать утилиту unbuffer, которая является частью expect (expect-devel). unbuffer - очень короткий ожидаемый script. Он не нуждается в LD_PRELOAD, потому что он использует другой трюк. expect создает псевдотерминал (например, xterm или ssh), поэтому процесс, выполняемый с помощью unbuffer, обманывается, думая, что он пишет интерактивное устройство, поэтому по умолчанию он использует буферизацию строки на stdout.

Использование в вашем случае:

unbuffer ./writer | ./reader

Если stdbuf работает с программой unbuffer, она также будет работать с высокой вероятностью. Поскольку LD_PRELOAD создает некоторые ограничения, unbuffer имеет преимущества перед stdbuf. В отличие от stdbuf он будет работать с этими типами исполняемых файлов:

  • УИП
  • с возможностями файла
  • статически связанный
  • не используется стандартный libc