1. 程式人生 > >kptr_restrict 向用戶空間核心中的指標(/proc/kallsyms-modules顯示value全部為0)

kptr_restrict 向用戶空間核心中的指標(/proc/kallsyms-modules顯示value全部為0)

知識共享許可協議

因本人技術水平和知識面有限, 內容如有紕漏或者需要修正的地方, 歡迎大家指正, 也歡迎大家提供一些其他好的除錯工具以供收錄, 鄙人在此謝謝啦

1 /proc/kallsyms顯示value全部為0

今天一個同事問我 cat /proc/kallsyms 顯示 value 全部為 0. 我在手機端試了一下, 果然如此.

切換到 root 使用者執行, 依然是 0. 感到十分奇怪, 因為核心發生 crash 或者開啟 trace 的時候, 都是呼叫的 sprint_ symbol 來列印的. 為啥核心可以, 使用者態 cat 就不行呢?

後來發現是系統為了保護這些符號地址洩露, 而用的一種保護手段, 從而使除 root

使用者外的普通使用者不能直接檢視符號地址.

2 kptr_restrict 介紹

原因在於核心檔案 kallsyms.c 中的顯示符號地址命令中做了如下限制.

seq_printf(m, "%pK %c %s\n", (void *)iter->value, iter->type, iter->name);

只需要把其中的 %pK 換成 %p 就可以讓普通使用者檢視符號地址了. 很多提權漏洞一般會用到此處的修改來獲取符號地址

核心提供控制變數 /proc/sys/kernel/kptr_restrict 來進行修改. 從核心文件 Documentation/sysctl/kernel.txt

中可以看到 kptr_restrict 用於控制核心的一些輸出列印.

Documentation/printk-formats.txt 有更加詳細的描述, 除了我們平時遇到的一些列印格式之外, 還有一些比較特殊的格式(我以前沒注意到).

==============================================================

kptr_restrict:

This toggle indicates whether restrictions are placed on
exposing kernel addresses via /proc and other interfaces.

When kptr_restrict is set
to (0), the default, there are no restrictions. When kptr_restrict is set to (1), kernel pointers printed using the %pK format specifier will be replaced with 0's unless the user has CAP_SYSLOG and effective user and group ids are equal to the real ids. This is because %pK checks are done at read() time rather than open() time, so if permissions are elevated between the open() and the read() (e.g via a setuid binary) then %pK will not leak kernel pointers to unprivileged users. Note, this is a temporary solution only. The correct long-term solution is to do the permission checks at open() time. Consider removing world read permissions from files that use %pK, and using dmesg_restrict to protect against uses of %pK in dmesg(8) if leaking kernel pointer values to unprivileged users is a concern. When kptr_restrict is set to (2), kernel pointers printed using %pK will be replaced with 0's regardless of privileges. ==============================================================
kptr_restrict 許可權描述
2 核心將符號地址列印為全0, root和普通使用者都沒有許可權
1 root使用者有許可權讀取, 普通使用者沒有許可權
0 root和普通使用者都可以讀取

kptr_restrict 值為 2 時, 所有使用者都無法讀取核心符號地址.

<code>kptr_restrict</code> 值為 <code>2</code> 時

kptr_restrict 值為 1 時, 普通使用者都無法讀取核心符號地址, root 使用者可以檢視.

<code>kptr_restrict</code> 值為 <code>1</code> 時

kptr_restrict 值為 0 時, 所有使用者都可以讀取核心地址.

<code>kptr_restrict</code> 值為0時

注意 kptr_restrict 對核心中很多地址和符號表的資訊匯出都有影響, 比如 /proc/modules 等.

3 kptr_restrict的設計

#http://elixir.free-electrons.com/linux/v4.13.9/source/lib/vsprintf.c#L1708
    case 'K':
        switch (kptr_restrict) {
        case 0:
            /* Always print %pK values */
            break;
        case 1: {
            const struct cred *cred;

            /*
             * kptr_restrict==1 cannot be used in IRQ context
             * because its test for CAP_SYSLOG would be meaningless.
             */
            if (in_irq() || in_serving_softirq() || in_nmi()) {
                if (spec.field_width == -1)
                    spec.field_width = default_width;
                return string(buf, end, "pK-error", spec);
            }

            /*
             * Only print the real pointer value if the current
             * process has CAP_SYSLOG and is running with the
             * same credentials it started with. This is because
             * access to files is checked at open() time, but %pK
             * checks permission at read() time. We don't want to
             * leak pointer values if a binary opens a file using
             * %pK and then elevates privileges before reading it.
             */
            cred = current_cred();
            if (!has_capability_noaudit(current, CAP_SYSLOG) ||
                !uid_eq(cred->euid, cred->uid) ||
                !gid_eq(cred->egid, cred->gid))
                ptr = NULL;
            break;
        }
        case 2:
        default:
            /* Always print 0's for %pK */
            ptr = NULL;
            break;
        }
        break;

4 參考