故障注入能力基礎設施

另請參閱 drivers/md/md-faulty.c 和 scsi_debug 的“every_nth”模組選項。

可用的故障注入能力

  • failslab

    注入 slab 分配失敗。 (kmalloc(), kmem_cache_alloc(), ...)

  • fail_page_alloc

    注入頁面分配失敗。 (alloc_pages(), get_free_pages(), ...)

  • fail_usercopy

    注入使用者記憶體訪問函式的失敗。 (copy_from_user(), get_user(), ...)

  • fail_futex

    注入 futex 死鎖和 uaddr 錯誤。

  • fail_sunrpc

    注入核心 RPC 客戶端和伺服器失敗。

  • fail_make_request

    在透過設定 /sys/block/<device>/make-it-fail 或 /sys/block/<device>/<partition>/make-it-fail 允許的裝置上注入磁碟 IO 錯誤。 (submit_bio_noacct())

  • fail_mmc_request

    在透過設定 /sys/kernel/debug/mmc0/fail_mmc_request 下的 debugfs 條目允許的裝置上注入 MMC 資料錯誤。

  • fail_function

    在特定函式上注入錯誤返回,這些函式透過 ALLOW_ERROR_INJECTION() 宏標記,透過設定 /sys/kernel/debug/fail_function 下的 debugfs 條目。 不支援引導選項。

  • fail_skb_realloc

    將 skb (套接字緩衝區) 重新分配事件注入到網路路徑中。 主要目標是識別和防止與網路子系統中的指標管理不善相關的問題。 透過在戰略點強制 skb 重新分配,此功能會建立現有 skb 標頭的指標變為無效的情況。

    當注入故障並觸發重新分配時,skb 標頭和資料的快取指標不再引用有效的記憶體位置。 這種有意的失效有助於暴露在重新分配事件後忽略正確指標更新的程式碼路徑。

    透過建立這些受控的故障場景,系統可以捕獲使用過時指標的例項,從而可能導致記憶體損壞或系統不穩定。

    要選擇要操作的介面,請將網路名稱寫入 /sys/kernel/debug/fail_skb_realloc/devname。 如果此欄位留空 (這是預設值),則將在所有網路介面上強制執行 skb 重新分配。

    當啟用 KASAN 時,這種故障檢測的有效性會得到增強,因為它有助於識別無效的記憶體引用和釋放後使用 (UAF) 問題。

  • NVMe故障注入

    透過設定 /sys/kernel/debug/nvme*/fault_inject 下的 debugfs 條目,在允許的裝置上注入 NVMe 狀態程式碼和重試標誌。 預設狀態程式碼是 NVME_SC_INVALID_OPCODE,不重試。 可以透過 debugfs 設定狀態程式碼和重試標誌。

  • Null test 塊驅動程式故障注入

    透過設定 /sys/kernel/config/nullb/<disk>/timeout_inject 下的配置項來注入 IO 超時,透過設定 /sys/kernel/config/nullb/<disk>/requeue_inject 下的配置項來注入重新排隊請求,以及透過設定 /sys/kernel/config/nullb/<disk>/init_hctx_fault_inject 下的配置項來注入 init_hctx() 錯誤。

配置故障注入能力的行為

debugfs 條目

fault-inject-debugfs 核心模組提供了一些 debugfs 條目,用於執行時配置故障注入能力。

  • /sys/kernel/debug/fail*/probability

    故障注入的可能性,以百分比表示。

    格式:<percent>

    請注意,對於某些測試用例,百分之一的失敗率是一個非常高的錯誤率。 考慮設定 probability=100 併為這些測試用例配置 /sys/kernel/debug/fail*/interval。

  • /sys/kernel/debug/fail*/interval

    指定失敗之間的間隔,用於透過所有其他測試的 should_fail() 呼叫。

    請注意,如果您啟用此功能,透過設定 interval>1,您可能需要設定 probability=100。

  • /sys/kernel/debug/fail*/times

    指定最多可能發生多少次失敗。 值為 -1 表示“無限制”。

  • /sys/kernel/debug/fail*/space

    指定初始資源“預算”,在每次呼叫 should_fail(,size) 時減小“size”。 在“space”達到零之前,將抑制故障注入。

  • /sys/kernel/debug/fail*/verbose

    格式:{ 0 | 1 | 2 }

    指定注入故障時訊息的詳細程度。“0”表示沒有訊息;“1”將僅列印每個故障的單個日誌行;“2”也將列印呼叫跟蹤 - 用於除錯故障注入顯示的問題。

  • /sys/kernel/debug/fail*/task-filter

    格式:{ ‘Y’ | ‘N’ }

    值為“N”會停用按程序過濾(預設值)。 任何正值都將失敗限制為僅由 /proc/<pid>/make-it-fail==1 指示的程序。

  • /sys/kernel/debug/fail*/require-start, /sys/kernel/debug/fail*/require-end, /sys/kernel/debug/fail*/reject-start, /sys/kernel/debug/fail*/reject-end

    指定堆疊跟蹤期間測試的虛擬地址範圍。 僅當行走的堆疊跟蹤中的某個呼叫者位於所需範圍內,並且沒有一個位於拒絕範圍內時,才會注入失敗。 預設所需範圍是 [0,ULONG_MAX)(整個虛擬地址空間)。 預設拒絕範圍是 [0,0)。

  • /sys/kernel/debug/fail*/stacktrace-depth

    指定在 [require-start,require-end) OR [reject-start,reject-end) 中搜索呼叫者期間行走的堆疊跟蹤最大深度。

  • /sys/kernel/debug/fail_page_alloc/ignore-gfp-highmem

    格式:{ ‘Y’ | ‘N’ }

    預設為“Y”,將其設定為“N”也會將失敗注入到 highmem/user 分配中(__GFP_HIGHMEM 分配)。

  • /sys/kernel/debug/failslab/cache-filter

    格式:{ ‘Y’ | ‘N’ }

    預設為“N”,將其設定為“Y”將僅在從某些快取請求物件時注入失敗。

    透過將“1”寫入 /sys/kernel/slab/<cache>/failslab 來選擇快取

  • /sys/kernel/debug/failslab/ignore-gfp-wait

  • /sys/kernel/debug/fail_page_alloc/ignore-gfp-wait

    格式:{ ‘Y’ | ‘N’ }

    預設為“Y”,將其設定為“N”也會將失敗注入到可以休眠的分配中(__GFP_DIRECT_RECLAIM 分配)。

  • /sys/kernel/debug/fail_page_alloc/min-order

    指定要注入失敗的最小頁面分配階數。

  • /sys/kernel/debug/fail_futex/ignore-private

    格式:{ ‘Y’ | ‘N’ }

    預設為“N”,將其設定為“Y”將在處理私有(地址空間)futexes 時停用失敗注入。

  • /sys/kernel/debug/fail_sunrpc/ignore-client-disconnect

    格式:{ ‘Y’ | ‘N’ }

    預設為“N”,將其設定為“Y”將停用 RPC 客戶端上的斷開連線注入。

  • /sys/kernel/debug/fail_sunrpc/ignore-server-disconnect

    格式:{ ‘Y’ | ‘N’ }

    預設為“N”,將其設定為“Y”將停用 RPC 伺服器上的斷開連線注入。

  • /sys/kernel/debug/fail_sunrpc/ignore-cache-wait

    格式:{ ‘Y’ | ‘N’ }

    預設為“N”,將其設定為“Y”將停用 RPC 伺服器上的快取等待注入。

  • /sys/kernel/debug/fail_function/inject

    格式:{ ‘function-name’ | ‘!function-name’ | ‘’ }

    按名稱指定錯誤注入的目標函式。 如果函式名稱帶有“!”字首,則從注入列表中刪除給定的函式。 如果未指定任何內容(‘’),則清除注入列表。

  • /sys/kernel/debug/fail_function/injectable

    (只讀)顯示可注入錯誤的函式以及可以指定的錯誤值型別。 錯誤型別將是以下之一; - NULL:retval 必須為 0。 - ERRNO:retval 必須為 -1 到 -MAX_ERRNO (-4096)。 - ERR_NULL:retval 必須為 0 或 -1 到 -MAX_ERRNO (-4096)。

  • /sys/kernel/debug/fail_function/<function-name>/retval

    指定要注入到給定函式的“錯誤”返回值。 這將在使用者指定新的注入條目時建立。 請注意,此檔案僅接受無符號值。 因此,如果您想使用負 errno,最好使用“printf”而不是“echo”,例如:$ printf %#x -12 > retval

  • /sys/kernel/debug/fail_skb_realloc/devname

    指定要在其上強制執行 SKB 重新分配的網路介面。 如果留空,SKB 重新分配將應用於所有網路介面。

    用法示例

    # Force skb reallocation on eth0
    echo "eth0" > /sys/kernel/debug/fail_skb_realloc/devname
    
    # Clear the selection and force skb reallocation on all interfaces
    echo "" > /sys/kernel/debug/fail_skb_realloc/devname
    

引導選項

為了在 debugfs 不可用時(早期啟動時間)注入故障,請使用引導選項

failslab=
fail_page_alloc=
fail_usercopy=
fail_make_request=
fail_futex=
fail_skb_realloc=
mmc_core.fail_request=<interval>,<probability>,<space>,<times>

proc 條目

  • /proc/<pid>/fail-nth, /proc/self/task/<tid>/fail-nth

    將整數 N 寫入此檔案會使任務中的第 N 次呼叫失敗。 從該檔案讀取會返回一個整數值。 值為“0”表示已注入使用先前寫入此檔案的故障設定。 正整數 N 表示尚未注入故障。 請注意,此檔案啟用所有型別的故障(slab、futex 等)。 此設定優先於所有其他通用 debugfs 設定,例如機率、間隔、次數等。但是,每個功能的設定(例如 fail_futex/ignore-private)優先於它。

    此功能旨在用於系統地測試單個系統呼叫中的故障。 請參見下面的示例。

可注入錯誤的函式

此部分適用於考慮將函式新增到 ALLOW_ERROR_INJECTION() 宏的核心開發人員。

可注入錯誤函式的要求

由於函式級別的錯誤注入強制更改程式碼路徑,即使輸入和條件正確也會返回錯誤,如果在 NOT 錯誤可注入的函式上允許錯誤注入,這可能會導致意外的核心崩潰。 因此,您(和稽核者)必須確保;

  • 如果函式失敗,則返回錯誤程式碼,並且呼叫者必須正確檢查它(需要從中恢復)。

  • 該函式在第一次錯誤返回之前不會執行任何可以更改任何狀態的程式碼。 狀態包括全域性或本地狀態,或輸入變數。 例如,清除輸出地址儲存(例如*ret = NULL),遞增/遞減計數器,設定標誌,搶佔/irq 停用或獲取鎖(如果在返回錯誤之前恢復了這些,那將是可以的。)

第一個要求很重要,它會導致釋放(釋放物件)函式通常比分配函式更難注入錯誤。 如果未正確處理此類釋放函式的錯誤,則容易導致記憶體洩漏(呼叫者會混淆物件已被釋放或損壞。)

第二個是針對期望該函式始終執行某些操作的呼叫者。 因此,如果函式錯誤注入跳過整個函式,則期望會落空,並導致意外的錯誤。

可注入錯誤函式的型別

每個可注入錯誤的函式都將具有 ALLOW_ERROR_INJECTION() 宏指定的錯誤型別。 如果新增新的可注入錯誤函式,則必須仔細選擇它。 如果選擇了錯誤的錯誤型別,則核心可能會崩潰,因為它可能無法處理該錯誤。 在 include/asm-generic/error-injection.h 中定義了 4 種類型的錯誤

EI_ETYPE_NULL

如果此函式失敗,將返回 NULL。 例如,返回已分配的物件地址。

EI_ETYPE_ERRNO

如果此函式失敗,將返回 -errno 錯誤程式碼。 例如,如果輸入錯誤,則返回 -EINVAL。 這將包括透過 ERR_PTR() 宏返回編碼 -errno 的地址的函式。

EI_ETYPE_ERRNO_NULL

如果此函式失敗,將返回 -errnoNULL。 如果此函式的呼叫者使用 IS_ERR_OR_NULL() 宏檢查返回值,則此型別將是合適的。

EI_ETYPE_TRUE

如果此函式失敗,將返回 true(非零正值)。

如果您指定了錯誤的型別,例如,對於返回已分配物件的函式指定 EI_TYPE_ERRNO,則可能會導致問題,因為返回的值不是物件地址,並且呼叫者無法訪問該地址。

如何新增新的故障注入能力

  • #include <linux/fault-inject.h>

  • 定義故障屬性

    DECLARE_FAULT_ATTR(name);

    有關詳細資訊,請參見 fault-inject.h 中 struct fault_attr 的定義。

  • 提供一種配置故障屬性的方法

  • 引導選項

    如果需要從啟動時啟用故障注入能力,則可以提供引導選項來配置它。 有一個用於它的輔助函式

    setup_fault_attr(attr, str);

  • debugfs 條目

    failslab、fail_page_alloc、fail_usercopy 和 fail_make_request 使用此方法。 輔助函式

    fault_create_debugfs_attr(name, parent, attr);

  • 模組引數

    如果故障注入能力的範圍僅限於單個核心模組,則最好提供模組引數來配置故障屬性。

  • 新增一個鉤子來插入失敗

    當 should_fail() 返回 true 時,客戶端程式碼應注入失敗

    should_fail(attr, size);

應用示例

  • 將 slab 分配失敗注入到模組 init/exit 程式碼中

    #!/bin/bash
    
    FAILTYPE=failslab
    echo Y > /sys/kernel/debug/$FAILTYPE/task-filter
    echo 10 > /sys/kernel/debug/$FAILTYPE/probability
    echo 100 > /sys/kernel/debug/$FAILTYPE/interval
    echo -1 > /sys/kernel/debug/$FAILTYPE/times
    echo 0 > /sys/kernel/debug/$FAILTYPE/space
    echo 2 > /sys/kernel/debug/$FAILTYPE/verbose
    echo Y > /sys/kernel/debug/$FAILTYPE/ignore-gfp-wait
    
    faulty_system()
    {
        bash -c "echo 1 > /proc/self/make-it-fail && exec $*"
    }
    
    if [ $# -eq 0 ]
    then
        echo "Usage: $0 modulename [ modulename ... ]"
        exit 1
    fi
    
    for m in $*
    do
        echo inserting $m...
        faulty_system modprobe $m
    
        echo removing $m...
        faulty_system modprobe -r $m
    done
    

  • 僅針對特定模組注入頁面分配失敗

    #!/bin/bash
    
    FAILTYPE=fail_page_alloc
    module=$1
    
    if [ -z $module ]
    then
        echo "Usage: $0 <modulename>"
        exit 1
    fi
    
    modprobe $module
    
    if [ ! -d /sys/module/$module/sections ]
    then
        echo Module $module is not loaded
        exit 1
    fi
    
    cat /sys/module/$module/sections/.text > /sys/kernel/debug/$FAILTYPE/require-start
    cat /sys/module/$module/sections/.data > /sys/kernel/debug/$FAILTYPE/require-end
    
    echo N > /sys/kernel/debug/$FAILTYPE/task-filter
    echo 10 > /sys/kernel/debug/$FAILTYPE/probability
    echo 100 > /sys/kernel/debug/$FAILTYPE/interval
    echo -1 > /sys/kernel/debug/$FAILTYPE/times
    echo 0 > /sys/kernel/debug/$FAILTYPE/space
    echo 2 > /sys/kernel/debug/$FAILTYPE/verbose
    echo Y > /sys/kernel/debug/$FAILTYPE/ignore-gfp-wait
    echo Y > /sys/kernel/debug/$FAILTYPE/ignore-gfp-highmem
    echo 10 > /sys/kernel/debug/$FAILTYPE/stacktrace-depth
    
    trap "echo 0 > /sys/kernel/debug/$FAILTYPE/probability" SIGINT SIGTERM EXIT
    
    echo "Injecting errors into the module $module... (interrupt to stop)"
    sleep 1000000
    

  • 在 btrfs 掛載時注入 open_ctree 錯誤

    #!/bin/bash
    
    rm -f testfile.img
    dd if=/dev/zero of=testfile.img bs=1M seek=1000 count=1
    DEVICE=$(losetup --show -f testfile.img)
    mkfs.btrfs -f $DEVICE
    mkdir -p tmpmnt
    
    FAILTYPE=fail_function
    FAILFUNC=open_ctree
    echo $FAILFUNC > /sys/kernel/debug/$FAILTYPE/inject
    printf %#x -12 > /sys/kernel/debug/$FAILTYPE/$FAILFUNC/retval
    echo N > /sys/kernel/debug/$FAILTYPE/task-filter
    echo 100 > /sys/kernel/debug/$FAILTYPE/probability
    echo 0 > /sys/kernel/debug/$FAILTYPE/interval
    echo -1 > /sys/kernel/debug/$FAILTYPE/times
    echo 0 > /sys/kernel/debug/$FAILTYPE/space
    echo 1 > /sys/kernel/debug/$FAILTYPE/verbose
    
    mount -t btrfs $DEVICE tmpmnt
    if [ $? -ne 0 ]
    then
        echo "SUCCESS!"
    else
        echo "FAILED!"
        umount tmpmnt
    fi
    
    echo > /sys/kernel/debug/$FAILTYPE/inject
    
    rmdir tmpmnt
    losetup -d $DEVICE
    rm testfile.img
    

  • 僅注入 skbuff 分配失敗

    # mark skbuff_head_cache as faulty
    echo 1 > /sys/kernel/slab/skbuff_head_cache/failslab
    # Turn on cache filter (off by default)
    echo 1 > /sys/kernel/debug/failslab/cache-filter
    # Turn on fault injection
    echo 1 > /sys/kernel/debug/failslab/times
    echo 1 > /sys/kernel/debug/failslab/probability
    

使用 failslab 或 fail_page_alloc 執行命令的工具

為了更容易完成上述任務,我們可以使用 tools/testing/fault-injection/failcmd.sh。 請執行命令“./tools/testing/fault-injection/failcmd.sh --help”以獲取更多資訊,並檢視以下示例。

示例

透過注入 slab 分配失敗來執行命令“make -C tools/testing/selftests/ run_tests”

# ./tools/testing/fault-injection/failcmd.sh \
        -- make -C tools/testing/selftests/ run_tests

與上述相同,除了指定最多 100 次失敗,而不是預設最多一次

# ./tools/testing/fault-injection/failcmd.sh --times=100 \
        -- make -C tools/testing/selftests/ run_tests

與上述相同,除了注入頁面分配失敗而不是 slab 分配失敗

# env FAILCMD_TYPE=fail_page_alloc \
        ./tools/testing/fault-injection/failcmd.sh --times=100 \
        -- make -C tools/testing/selftests/ run_tests

使用 fail-nth 進行系統性故障測試

以下程式碼系統地將 socketpair() 系統呼叫中的第 0 次、第 1 次、第 2 次等能力設為故障

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

int main()
{
      int i, err, res, fail_nth, fds[2];
      char buf[128];

      system("echo N > /sys/kernel/debug/failslab/ignore-gfp-wait");
      sprintf(buf, "/proc/self/task/%ld/fail-nth", syscall(SYS_gettid));
      fail_nth = open(buf, O_RDWR);
      for (i = 1;; i++) {
              sprintf(buf, "%d", i);
              write(fail_nth, buf, strlen(buf));
              res = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
              err = errno;
              pread(fail_nth, buf, sizeof(buf), 0);
              if (res == 0) {
                      close(fds[0]);
                      close(fds[1]);
              }
              printf("%d-th fault %c: res=%d/%d\n", i, atoi(buf) ? 'N' : 'Y',
                      res, err);
              if (atoi(buf))
                      break;
      }
      return 0;
}

示例輸出

1-th fault Y: res=-1/23
2-th fault Y: res=-1/23
3-th fault Y: res=-1/12
4-th fault Y: res=-1/12
5-th fault Y: res=-1/23
6-th fault Y: res=-1/23
7-th fault Y: res=-1/23
8-th fault Y: res=-1/12
9-th fault Y: res=-1/12
10-th fault Y: res=-1/12
11-th fault Y: res=-1/12
12-th fault Y: res=-1/12
13-th fault Y: res=-1/12
14-th fault Y: res=-1/12
15-th fault Y: res=-1/12
16-th fault N: res=0/12