前一陣子上班時, Samuel 跑過來問:

“咦? 你們之前寫某個 Server 的時候, 怎麼讓程式自己產生 Core dump 啊?”

“就是用 setrlimit(2) 的啊!”

“那幫我看一下為啥我照著那樣寫不會動..”

沒想到就開始了殘酷的惡夢~ 讓我們花費了不少工夫才發現為甚麼.

在 Linux 上, 預設是不會有 Core dump 的, 而要讓程式產生 Core dump 的方法就是利用 bash built-in 的 ulimit 指令. 我去年這篇 How to enlarge Coredump Size and File Descriptor Limitations 剛好也有寫到.

不過意外常常有, 所以除了讓 System administrator 設 ulimit -c unlimited 之外, 我自己也會在程式裡面利用 setrlimit(2) 這隻 system call, 讓程式在執行時, 能夠不管 administrator 有沒有用 ulimit 設定 Core dump size, 保證一定會產生 Core dump. 在程式 crash 的時候, Core dump 是很重要線索啊! 就像 CSI 一樣, 讓證據會說話.

可是在看完 Samuel 的程式時, 我和他兩個就覺得很奇怪, 應該是會 work 才對, 因為同樣的 code 寫的程式已經在 production server 上跑了一段時間, 應該不會有問題才對, 這時候, 我才猛然想起來, 管機器的 administrator 曾說過有時候 Core dump 不會出現. “God! 該不會就是同一個問題吧?”

和 Samuel 兩個人找完資料的答案, 就是寫這篇文章的動機了. 我們發現, 如果一個程式按照上面的方法都無法產生 Core dump, 那要看看這個程式是否是用了 setuid(2) 這個 System call. 我們發現, 一個 setuid(2)seteuid(2) 過後的程式, 是沒有辦法產生 Core dump 的.

好巧不巧, 通常 Server 為了一些安全性的考量, 也會實作 setuid(2) 或 seteuid(2) 來達到 Running with Least Privilege (相對應 Windows 的指令就是 Run As). 也就是說, setuid(2) 一定是不能略過的. 解決方法有兩種.

第一種是只有這個程式有效. 用的方法就是 prctl(2). 這個方法是可以改 source, 然後 rebuild 的狀況下才能用. Sample code 如下:

#include <stdio.h>;
#include <stdlib.h>;
#include <errno.h>;
#include <unistd.h>;
#include <pwd.h>;
#include <sys/resource.h>;
#include <sys/prctl.h>;

#define SU_USER "nobody"

int main(void)
{
    struct rlimit corelimit;
    struct passwd *pw = NULL;
    char          *cp = NULL;

    /* if switch to nobody failed */
    if (NULL == (pw = getpwnam(SU_USER)))
    {
        fprintf(stderr, "Cannot get uid from user(%s). Error: %s\n",
                         SU_USER, strerror(errno));
        return -1;
    }

    /* try to switch to nobody */
    if ((setuid(pw->pw_uid) < 0) || (seteuid(pw->pw_uid) < 0))
    {
        fprintf(stderr, "Cannot switch to user(%s). Error: %s\n",
                         SU_USER, strerror(errno));
        return -2;
    }

    /* force to make coredump */
    if (prctl(PR_SET_DUMPABLE, 1) < 0)
    {
        fprintf(stderr, "Cannot enable core dumping. Error: %s\n",
                         strerror(errno));
        return -3;
    }

    /* set core size to unlimited */
    corelimit.rlim_cur = RLIM_INFINITY;
    corelimit.rlim_max = RLIM_INFINITY;
    if (setrlimit(RLIMIT_CORE, &amp;corelimit) < 0)
    {
        fprintf(stderr, "Setrlimit failed! Error: %s\n",
                         strerror(errno));
        return -4;
    }

    /* force to coredump */
    *cp = '1';

    return 0;
}

這種方法要注意的事情有:

  • 一定要用 root 執行.
    setuid 然後讓一般 user 去執行的方法不行.
    (e.g. sudo chown root a.out; sudo chgrp root a.out; sudo chmod 4755 a.out)
  • 因為 Core dump 產生的目錄和執行檔在同一個目錄下, 這個目錄的權限一定要是 root 這個 user, root 這個 group 同時都可以寫入才行. 要注意這個 case, 不要把 root 當成是 super-user, 而是要當作 regular user.
    比方說, (drwxrwxr-x, root root) 和 (drwxrwxrwx, nobody nogroup) 可以, 但是 (drwxr-xr-x root root) 和 (drwxrwxr-x root nogroup) 都不行. 很弔詭吧! 我也不知道為甚麼~
  • 第二種方法是 system-wide 的, 也就是會影響到所有在系統執行的程式. 方法就是 /proc/sys/fs/suid_dumpable. 因為這個和 kernel 的版本有關, 請 man 5 proc 比較保險. 這是在無法動 source code, 而且原程式沒有用 prctl(2) 情況下的殺手鐧.

    要注意的事情和第一種方法一樣, 把 /proc/sys/fs/suid_dumpable 設成 1 或 2 的差別, 是會決定 core dump file 的擁有者. 如果設成 1, 就會和第一種方法一樣是被 switch 的 owner (本例是 nobody). 如果設 2 就一定是 root, 但是這個似乎要 kernel 2.6.13 以上才有 support? 我不是很確定.

    希望這篇文章給大家當作個參考囉!

    PS: 這篇竟然寫了快二個周末, 真是夠久的~

Popularity: 34% [?]