我试图oom_adjust_setup
在OpenSSH的port_linux.c
启发下为一个过程设置OOM杀手分数调整.为此,我打开/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
Run Code Online (Sandbox Code Playgroud)
但是当我的进程确实具有该功能时,我甚至无法打开该文件:它与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
Run Code Online (Sandbox Code Playgroud)
为什么这样做?我错过了什么?
一些进一步的谷歌搜索让我在2013年10月20日由Azat Khuzhin发表这篇文章.显然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;
}
Run Code Online (Sandbox Code Playgroud)
dvk*_*dvk 13
这个非常有趣破解,花了我一段时间.
第一个真正的提示是对另一个问题的回答:https://unix.stackexchange.com/questions/364568/how-to-read-the-proc-pid-fd-directory-of-a-process-which- has-a-linux-capabil - 只是想给予信任.
/proc/self/
如果进程具有任何功能,那么您获得"权限被拒绝"的真正原因是根目录下拥有的文件- 它不是CAP_SYS_RESOURCE
关于oom_*
文件的具体内容.您可以通过调用stat
和使用不同的功能来验证这一点.引用man 5 proc
:
的/ proc/[PID]
每个正在运行的进程都有一个数字子目录; 子目录由进程ID命名.
每个/ proc/[pid]子目录都包含下面描述的伪文件和目录.这些文件通常由进程的有效用户和有效组ID拥有.但是,作为安全措施,如果进程的"dumpable"属性设置为1以外的值,则将所有权设为root:root.此属性可能会因以下原因而更改:
通过prctl(2)PR_SET_DUMPABLE操作显式设置该属性.
该属性被复位以在文件/ proc/SYS/FS/suid_dumpable(下面描述)的值,对于使用prctl(2)描述的原因.
将"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);
Run Code Online (Sandbox Code Playgroud)
希望有所帮助;)