我试图为一个过程设置OOM杀手分数调整,受到启发 oom_adjust_setup
在OpenSSH中 port_linux.c
。为此,我打开 /proc/self/oom_score_adj
,读取旧值,并写入新值。显然,我的流程需要是root或具有能力 CAP_SYS_RESOURCE
要做到这一点。
我得到的结果是我无法解释的。当我的进程没有该功能时,我能够打开该文件并读取和写入值,尽管我写的值没有生效(足够公平):
$ ./a.out
CAP_SYS_RESOURCE: not effective, not permitted, not inheritable
oom_score_adj value: 0
wrote 5 bytes
oom_score_adj value: 0
但是当我的过程 不 有能力,我 甚至无法打开 文件:它与EACCES失败:
$ sudo setcap CAP_SYS_RESOURCE+eip a.out
$ ./a.out
CAP_SYS_RESOURCE: effective, permitted, not inheritable
failed to open /proc/self/oom_score_adj: Permission denied
为什么这样做?我错过了什么?
一些进一步的谷歌搜索引导我 这篇lkml帖子由Azat Khuzhin于2013年10月20日发布。显然地 CAP_SYS_RESOURCE
让你改变 oom_score_adj
对于任何过程,但你自己。要更改自己的分数调整,您需要将其与 CAP_DAC_OVERRIDE
- 即禁用所有文件的访问控制。 (如果我想要的话,我会把这个程序设为setuid root。)
所以我的问题是,我怎样才能做到这一点 无 CAP_DAC_OVERRIDE
?
我正在运行Ubuntu xenial 16.04.4,内核版本4.13.0-45-generic。我的问题类似于但不同于 这个问题:那是关于错误的 write
,当没有能力。
我的示例程序:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/capability.h>
void read_value(FILE *fp)
{
int value;
rewind(fp);
if (fscanf(fp, "%d", &value) != 1) {
fprintf(stderr, "read failed: %s\n", ferror(fp) ? strerror(errno) : "cannot parse");
}
else {
fprintf(stderr, "oom_score_adj value: %d\n", value);
}
}
void write_value(FILE *fp)
{
int result;
rewind(fp);
result = fprintf(fp, "-1000");
if (result < 0) {
fprintf(stderr, "write failed: %s\n", strerror(errno));
}
else {
fprintf(stderr, "wrote %d bytes\n", result);
}
}
int main()
{
FILE *fp;
struct __user_cap_header_struct h;
struct __user_cap_data_struct d;
h.version = _LINUX_CAPABILITY_VERSION_3;
h.pid = 0;
if (0 != capget(&h, &d)) {
fprintf(stderr, "capget failed: %s\n", strerror(errno));
}
else {
fprintf(stderr, "CAP_SYS_RESOURCE: %s, %s, %s\n",
d.effective & (1 << CAP_SYS_RESOURCE) ? "effective" : "not effective",
d.permitted & (1 << CAP_SYS_RESOURCE) ? "permitted" : "not permitted",
d.inheritable & (1 << CAP_SYS_RESOURCE) ? "inheritable" : "not inheritable");
}
fp = fopen("/proc/self/oom_score_adj", "r+");
if (!fp) {
fprintf(stderr, "failed to open /proc/self/oom_score_adj: %s\n", strerror(errno));
return 1;
}
else {
read_value(fp);
write_value(fp);
read_value(fp);
fclose(fp);
}
return 0;
}
这个非常有趣的破解,花了我一段时间。
第一个真正的提示是对不同问题的回答: https://unix.stackexchange.com/questions/364568/how-to-read-the-proc-pid-fd-directory-of-a-process-which-has-a-linux-capabil - 只是想给予信任。
它不能正常工作的原因
你得到“许可被拒绝”的真正原因是有文件 /proc/self/
如果进程有,则由root拥有 任何 能力 - 它不是关于 CAP_SYS_RESOURCE
或者约 oom_*
文件具体。您可以通过致电来验证这一点 stat
并使用不同的功能。引用 man 5 proc
:
的/ proc / [PID]
每个正在运行的进程都有一个数字子目录;子目录由进程ID命名。
每个/ proc / [pid]子目录都包含下面描述的伪文件和目录。这些文件通常由进程的有效用户和有效组ID拥有。但是,作为安全措施,如果进程的“dumpable”属性设置为1以外的值,则将所有权设置为root:root。此属性可能会因以下原因而更改:
将“dumpable”属性重置为1会将/ proc / [pid] / *文件的所有权还原为进程的真实UID和实际GID。
这已经暗示了解决方案,但首先让我们深入挖掘并看到它 man prctl
:
PR_SET_DUMPABLE(自Linux 2.3.20起)
设置“dumpable”标志的状态,该标志确定在传递其默认行为是产生核心转储的信号时是否为调用进程生成核心转储。
在内核(包括2.6.12)中,arg2必须为0(SUID_DUMP_DISABLE,进程不可转储)或1(SUID_DUMP_USER,进程可转储)。在内核2.6.13和2.6.17之间,也允许值2,这导致通常不会被转储的任何二进制文件只能被root用户转储。出于安全原因,此功能已被删除。 (另请参阅proc(5)中/ proc / sys / fs / suid_dumpable的说明。)
通常,此标志设置为1.但是,在以下情况下,它将重置为文件/ proc / sys / fs / suid_dumpable(默认情况下值为0)中包含的当前值:
进程的有效用户或组ID已更改。
进程的文件系统用户或组ID已更改(请参阅凭据(7))。
该过程执行(execve(2))set-user-ID或set-group-ID程序,从而导致有效用户ID或有效组ID的更改。
该进程执行(execve(2))具有文件功能的程序(参见功能(7)),但前提是所获得的允许功能超过了该进程已经允许的功能。
无法转储的进程无法通过ptrace(2)PTRACE_ATTACH附加;有关详细信息,请参阅ptrace(2)。
如果进程不可转储,则进程的/ proc / [pid]目录中的文件所有权会受到影响,如proc(5)中所述。
现在很明显:我们的进程具有用于启动它的shell没有的功能,因此dumpable属性设置为false,因此 /proc/self/
由root而不是当前用户拥有。
如何使它工作
修复程序就像在尝试打开文件之前重新设置dumpable属性一样简单。在打开文件之前粘贴以下内容或类似内容:
prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
希望有所帮助;)
这不是答案(dvk已经提供了答案 对于所陈述的问题),但是延伸的评论描述了经常被忽视的,可能非常危险的副作用 /proc/self/oom_score_adj
。
总之,使用 prctl(PR_SET_DUMPABLE, 1, 0, 0, 0)
将允许具有CAP_SYS_RESOURCE功能的进程(通过例如文件系统功能传送)来修改 oom_score_adj
由同一用户拥有的任何其他进程,包括他们自己的进程。
(默认情况下,具有功能的进程不可转储,因此即使进程被处置为生成核心的信号杀死,也不会生成核心转储。)
我想评论的危险是,如何 oom_score_adj
范围 是继承的,以及为创建子进程的进程更改它意味着什么。 (感谢dvk进行了一些更正。)
Linux内核维护内部值, oom_score_adj_min
,对于每个过程。用户(或进程本身)可以修改 oom_score_adj
到任何值之间 oom_score_adj_min
和 OOM_SCORE_ADJ_MAX
。值越高,该过程被杀死的可能性越大。
创建进程时,它将继承它 oom_score_adj_min
来自其父母。所有进程的原始父进程init都有一个初始值 oom_score_adj_min
0。
减少 oom_score_adj
下面 oom_score_adj_min
,具有超级用户权限或具有CAP_SYS_RESOURCE且可转储的进程,将新分数写入 /proc/PID/oom_score_adj
。在这种情况下, oom_score_adj_min
也设置为相同的值。
(您可以通过检查来验证这一点 FS / PROC / base.c:__ set_oom_adj() 在Linux内核中;看到作业 task->signal->oom_score_adj_min
。)
问题是, oom_score_adj_min
值粘滞,除非由具有CAP_SYS_RESOURCE功能的进程更新。 (注意:我原本以为它根本不能被提出,但我错了。)
例如,如果您有一个具有它的高价值服务守护程序 oom_score_adj_min
减少,没有CAP_SYS_RESOURCE功能运行,增加了 oom_score_adj
在分叉子进程之前会导致子进程继承新进程 oom_score_adj
,但原来的 oom_score_adj_min
。这意味着这样的子进程可以减少他们的 oom_score_adj
到他们的父服务守护进程,没有任何特权或功能。
(因为只有两千一个可能 oom_score_adj
价值观(-1000
至 1000
,包括),只有一千个减少了一个进程被杀死的机会(负面的,零是默认的)与“默认”相比,一个邪恶的过程只需要做十或十一次写入 /proc/self/oom_score_adj
通过二进制搜索使OOM杀手尽可能地避免它:首先,它将尝试-500。如果成功,那么 oom_score_adj_min
介于-1000和-500之间。如果它失败了,那么 oom_score_adj_min
在-499和1000之间。通过在每次尝试时将范围减半,可以设置 oom_score_adj
到该进程的内核内部最小值, oom_score_adj_min
,在十或十一次写入中,取决于最初的内容 oom_score_adj
价值是。)
当然,有缓解和策略来避免继承问题。
例如,如果你有一个重要的进程,OOM杀手应该单独留下,不应该创建子进程,你应该使用一个专用的用户帐户来运行它。 RLIMIT_NPROC
设置为适当的小值。
如果您有一个创建新子进程的服务,但您希望父进程比其他进程更不可能被OOM杀死,并且您不希望子进程继承该进程,则有两种方法可行。
您的服务可以在启动时分叉子进程以创建进一步的子进程,然后再降低它 oom_score_adj
。这使得子进程继承了它们 oom_score_adj_min
(和 oom_score_adj
)从启动服务的过程。
您的服务可以保留 CAP_SYS_RESOURCE
在里面 CAP_PERMITTED
设置,但添加或删除它 CAP_EFFECTIVE
根据需要设置。
当。。。的时候 CAP_SYS_RESOURCE
在里面 CAP_EFFECTIVE
设置,调整 oom_score_adj
也设置了 oom_score_adj_min
达到同样的价值。
什么时候 CAP_SYS_RESOURCE
不在 CAP_EFFECTIVE
设置,你不能减少 oom_score_adj
低于相应的 oom_score_adj_min
。 oom_score_adj_min
即使在 oom_score_adj
被修改了。
将在OOM情况下可以取消/杀死的工作放入具有更高级别的子进程中是有意义的 oom_score_adj
值。如果确实发生了OOM情况 - 例如,在嵌入式设备上 - 核心服务守护程序具有更高的生存机会,即使工作者子进程被终止也是如此。当然,核心守护进程本身不应该为响应客户端请求分配动态内存,因为它中的任何错误可能不会使该守护进程崩溃,而是使整个系统停止(在OOM情况下基本上除了原始之外的所有内容)因为核心守护进程被杀死了。