代码之家  ›  专栏  ›  技术社区  ›  legoscia

当我有正确的能力时,无法打开/proc/self/oom-score-adj

  •  11
  • legoscia  · 技术社区  · 6 年前

    我正试图为一个过程设置OOM杀手得分调整,灵感来自 oom_adjust_setup in OpenSSH's port_linux.c .为此,我打开 /proc/self/oom_score_adj ,读取旧值,然后写入新值。显然,我的过程需要是根或具有能力 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
    

    但当我的过程 有能力,我 甚至不能打开 文件:失败,每个事件:

    $ 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
    

    为什么会这样?我错过了什么?


    我又咕噜了几声 this lkml post by Azat Khuzhin on 20 Oct 2013 .显然 Cap_Sys_资源 让你改变 oom_score_adj 除了你自己。要更改自己的分数调整,需要将其与 CAP_DAC_OVERRIDE -也就是说,禁用所有文件的访问控制。(如果我想要的话,我会把这个程序设为setuid root。)

    所以我的问题是,我如何才能做到这一点 没有 cap_dac_覆盖 是吗?


    我运行的是UbuntuXenial16.04.4,内核版本4.13.0-45-generic。我的问题类似但不同于 this question :这是关于一个错误 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;
    }
    
    2 回复  |  直到 6 年前
        1
  •  13
  •   dvk    6 年前

    这个很有趣,我花了一段时间。

    第一个真正的提示是对另一个问题的回答: 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 以下内容:

    /过程/[pid]

    每个正在运行的进程都有一个子目录,子目录由进程ID命名。

    每个/proc/[pid]子目录都包含下面描述的伪文件和目录。这些文件通常由流程的有效用户和有效组ID拥有。但是,作为一种安全措施,如果进程的“dumpable”属性设置为1以外的值,则所有权将设置为root:root。此属性可能由于以下原因而更改:

    • 属性是通过prctl(2)pr_set_dumpable操作显式设置的。

    • 由于prctl(2)中描述的原因,属性被重置为文件/proc/sys/fs/suid_dumpable(如下所述)中的值。

    将“dumpable”属性重置为1会将/proc/[pid]/*文件的所有权还原为进程的真实uid和真实gid。

    这已经暗示了解决方案,但首先让我们更深入地研究一下,看看 man prctl 以下内容:

    pr_set_dumpable(从Linux 2.3.20开始)

    设置“dumpable”标志的状态,该标志确定在传递默认行为为生成核心转储的信号时是否为调用进程生成核心转储。

    在2.6.12之前(含2.6.12)的内核中,arg2必须为0(suid_dump_disable,process is not dumpable)或1(suid_dump_user,process is dumpable)。在内核2.6.13和2.6.17之间,值2也被允许,这导致通常不会被转储的任何二进制文件只能被根目录读取;出于安全原因,这个特性被删除。(另请参见/proc/sys/fs/suid_dumpable in proc(5)的说明。)

    通常,此标志设置为1。但是,在以下情况下,它将重置为文件/proc/sys/fs/suid_dumpable(默认值为0)中包含的当前值:

    • 流程的有效用户或组ID已更改。

    • 进程的文件系统用户或组ID已更改(请参阅凭据(7))。

    • 该进程执行(execve(2))一个设置用户ID或设置组ID程序,从而更改有效用户ID或有效组ID。

    • 该进程执行(execve(2))一个具有文件功能的程序(见功能(7)),但前提是获得的允许功能超过了该进程已经允许的功能。

    无法通过ptrace(2)ptrace_attach附加不可转储的进程;有关详细信息,请参阅ptrace(2)。

    如果进程不可转储,那么进程的/proc/[pid]目录中文件的所有权将受到影响,如proc(5)中所述。

    现在已经很清楚了:我们的进程具有shell用来启动它的能力,因此dumpable属性被设置为false,因此 /过程/自身/ 由根用户而不是当前用户拥有。

    如何使其工作

    修复方法很简单,只需在尝试打开文件之前重新设置可转储属性。打开文件之前,请粘贴以下内容或类似内容:

    prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
    

    希望有帮助;)

        2
  •  2
  •   Nominal Animal    6 年前

    这不是答案( dvk already provided the answer 对所述问题),但一个扩展的评论描述了经常被忽视,可能非常危险的副作用,减少 /proc/self/oom_score_adj .

    总之,使用 prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) 将允许具有cap_sys_resource功能(通过文件系统功能传送)的进程修改 oom_score_adj 同一用户拥有的任何其他进程,包括自己的进程。

    (默认情况下,具有能力的进程是不可转储的,因此即使进程被部署为生成核心的信号杀死,也不会生成核心转储。)

    我想评论的危险是 OOM得分 范围 是继承的,以及为创建子进程的进程更改它意味着什么。(感谢dvk提供的一些更正。)


    Linux内核保持一个内部值, oom_score_adj_min ,对于每个过程。用户(或进程本身)可以修改 OOM得分 任何介于 OOM得分 OOM_SCORE_ADJ_MAX .值越高,进程被终止的可能性就越大。

    创建进程时,它将继承其 OOM得分 从它的父母那里。所有进程的原始父进程init具有初始值 OOM得分 第页,共0页。

    减少 OOM得分 在下面 OOM得分 ,一个具有超级用户特权或具有cap-sys-u资源且可转储的进程,将新分数写入 /proc/PID/oom_score_adj .在这种情况下, OOM得分 也设置为相同的值。

    (你可以通过检查 fs/proc/base.c:__set_oom_adj() 在Linux内核中;请参见 task->signal->oom_score_adj_min (第页)

    问题是 OOM得分 值保持不变,除非由具有cap-sys-u资源能力的进程更新。(注:我原以为根本不能提出,但我错了。)

    例如,如果您有一个具有 OOM得分 减少了,运行时没有cap_sys_资源能力,增加了 OOM得分 在分叉子进程之前,子进程将导致子进程继承新的 OOM得分 但是原始的 OOM得分 .这意味着这些子进程可以减少 OOM得分 到其父服务守护进程,没有任何特权或功能。

    (因为只有两千零一种可能 OOM得分 价值观( -1000 1000 ,包括在内),并且与“默认”相比,只有1000个进程减少了进程被杀死的机会(负的,0是默认值),一个恶意的进程只需要向 /proc/self/oom得分调整 为了让OOM杀手尽可能避免它,使用二进制搜索:首先,它将尝试-500。如果成功了, OOM得分 介于-1000和-500之间。如果失败了, OOM得分 介于-499和1000之间。通过在每次尝试时将范围减半,它可以设置 OOM得分 对于该进程的内核内部最小值, OOM得分 ,根据首字母 OOM得分 值为。)


    当然,还有一些缓解措施和策略来避免继承问题。

    例如,如果您有一个重要的进程,而OOM杀手不应该单独处理,这不应该创建子进程,那么您应该使用一个具有 RLIMIT_NPROC 设置为适当的小值。

    如果您有一个创建新子进程的服务,但是您希望父进程比其他进程更不容易被OOM杀死,并且您不希望子进程继承它,那么有两种方法可以工作。

    1. 您的服务可以在启动时分叉子进程,以便在降低子进程的 OOM得分 .这使得子进程继承 OOM得分 (和 OOM得分 )从启动服务的进程。

    2. 你的服务可以保持 CAP_SYS_RESOURCE CAP_PERMITTED 设置,但添加或从 CAP_EFFECTIVE 根据需要设置。

      Cap_Sys_资源 Cap_有效 设置、调整 OOM得分 同时设置 OOM得分 达到相同的值。

      什么时候? Cap_Sys_资源 不在 Cap_有效 设置,不能递减 OOM得分 在相应的 OOM得分 . OOM得分 即使在 OOM得分 已修改。

    将OOM情况下可以取消/取消的工作放入具有较高 OOM得分 价值观。如果确实发生了OOM情况(例如,在嵌入式设备上),那么核心服务守护进程存活的机会就要高得多,即使在工作子进程被杀死的情况下也是如此。当然,核心守护进程本身不应该为响应客户机请求而分配动态内存,因为其中的任何bug可能不仅会使该守护进程崩溃,而且会导致整个系统停止(在OOM情况下,除了最初的原因外,基本上所有的东西,核心守护进程都会被杀死)。