stdbuf与setuid/ capabilities

3
我正在阅读另一个生成输出的进程的输出(缓慢且无限)。因为我想实时读取这些数据,所以我使用“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 时,直到缓冲区被填满才会显示出内容。在命令前加入 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)

我希望了解为什么会发生这种情况以及如何在不以root身份运行的情况下继续使用stdbuf。我承认我不完全理解setuid在幕后所做的工作。


getline() 函数中使用了什么功能? - ott--
该示例使用GNU getline(可用于-std = gnu99),但使用fread的行为相同。 - ext
你用strace看过它了吗? - ott--
2个回答

3

不使用LD_PRELOAD的解决方案

您可以使用unbuffer实用程序,它是expect (expect-devel)软件包的一部分。 unbuffer是一个非常简短的expect脚本。 它不需要LD_PRELOAD,因为它使用另一种技巧。 expect创建一个伪终端(类似于xtermssh),因此使用unbuffer执行的进程被欺骗以认为它正在写入交互式设备,因此默认情况下在stdout上使用行缓冲。

在您的情况下使用方法:

unbuffer ./writer | ./reader

如果stdbuf可以与程序一起使用,那么unbuffer也很可能可以工作。由于LD_PRELOAD存在一些限制,unbufferstdbuf具有优势。与stdbuf相反,它将适用于以下类型的可执行文件:
  • setuid
  • 带有文件能力的
  • 静态链接的
  • 不使用标准的libc

你提到使用pty会使缓冲区变成行缓冲。这真正意味着什么?如果我有一个没有\n的无限行,那么永远不会有任何输出吗?还是有一些限制?如果完全关闭缓冲,那么缓冲区中最多只存在1个字节,这又怎么样? - CMCDragonkai
@CMCDragonkai 许多系统,包括GNU / Linux,尝试与ISO C兼容。有三种模式:不带缓冲(一切均尽快发送),完全缓冲(由其大小定义的缓冲区),行缓冲(缓冲单行文本)。对于行缓冲,缓冲区仍具有固定大小,因此当其填满时,会被刷新。不会发生“无限制”的极长行缓冲。请参阅http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf第7.21.3节文件。 - pabouk - Ukraine stay strong

3

从查看stdbuf源代码来看,它似乎通过设置LD_PRELOAD来工作。当然,在使用带有setuid可执行文件或sudo的LD_PRELOAD时存在安全问题。

我发现一个建议是禁用您的可执行文件的noatsecure selinux属性

另一个更简单的选择是避免使用stdbuf并直接从您的源代码调用fflush(stdout)


1
很遗憾,我无法控制作者的源代码,因此无法进行修改。但是感谢您对stdbuf的解释,禁止setuid的LD_PRELOAD是有道理的。 - ext
FreeBSD端口的stdbuf还有一个人页,展示了LD_PRELOAD的使用:libstdbuf - pabouk - Ukraine stay strong

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接