HN 日本語サマリー

← 一覧へ戻る
セキュリティ

メモリアクセス追跡とスタックベースの遅延注入による競合状態のテスト

Testing race conditions with mem access tracing and stack-based delay injection (projectzero.google)

8 pointsby mfrw0 コメント

要約

本記事では、マルチスレッド実行における競合状態(レースコンディション)の検出とデバッグを支援する新しいツールの開発について解説しています。これらのバグはセキュリティ上の問題を引き起こすことが多いですが、再現やテストケースの作成が困難です。提案されているツール群「MAccConc」は、Linuxカーネル向けに、メモリアクセス追跡と遅延注入を組み合わせることで、競合状態の自動テストや手動での探索を可能にします。

全文翻訳

メモリアクセス追跡とスタックベース遅延注入による競合状態のテスト

多くのセキュリティバグは競合状態(レースコンディション)であり、マルチスレッド実行が正しい順序で実行された場合にのみ、悪影響が現れます。これはいくつかのユースケースで課題となります。

  • 手動または静的解析で発見されたバグ候補の確認。
  • 回帰テスト:競合状態のバグを修正した後、テストスイートの一部としてバグを確実にトリガーする回帰テストを作成する良い方法がしばしばありません。
  • 自動バグ発見(ファジングなど):ファザーが並行操作の興味深い順列をすべて実行したり、操作が競合している場合にのみ実行されるコードパスに到達したりすることは困難です。

私は主にコードを読んでバグを発見します。バグを見つけたと思ったら、通常はテストケースを書いてバグの存在を証明または反証します。競合状態のバグの場合、どちらの結果を達成するのも難しいことがあります。Linuxカーネルのバグについては、適切な場所に条件付きのmdelay()呼び出し(指定された時間だけスピンループする)を追加してカーネルを再コンパイルすることがよくあります。通常、これらの呼び出しは実行中のスレッド名に基づいて条件付けますが、より複雑な条件が必要な場合もあります。DTraceをサポートするプラットフォーム(macOSやWindowsなど)では、同様の効果を得るためにchill()を呼び出すDTraceプローブを使用できますが、DTraceは命令ごとではなく、非インライン関数境界または明示的なトレースポイントでのみトレースできるため、その有用性は限定的です。プラットフォームに関係なく、このアプローチは時間がかかり、コードがバグっているかどうかを確実に判断するために試行錯誤が必要になる場合があります。

さらに、Linuxカーネルでは、競合状態のバグの修正には、問題のあるスレッドの順列を呼び出しグラフと関連メモリアクセスとともに示す手書きのASCIIダイアグラムが伴うことがよくあります(例:最近のrt_spin_unlock UAF修正や、最近のjbd2デッドロック修正を参照)。潜在的に脆弱なコードを分析し、同様の表現で結果を示す開発者ツールがあると便利でしょう。

要約

Linuxカーネルのテストケースにおけるマルチスレッド実行の順列を探索するためのツールを作成しました。

  • テストケースの可能なすべてのA-B-A順列を自動的にテストするツール。
  • 手動で順列を探索するためのターミナルUI。
  • 手動で順列を探索するためのGUI。

このカーネル部分は、ファジングによる競合状態の発見にも使用できることを意図していますが、それ用のユーザー空間ツールはまだ実装が必要です。

これらのツールは「MAccConc」(Memory Access Concurrencyの略)という名前でGitHubで公開されており、インストールと使用方法についてはREADMEを参照してください。

ツールの動作を見たいだけであれば、デモ:自動テストにスキップしてください。

ツールの理論にのみ興味がある場合は、実行ごとに安定したメモリアクセス識別子:カウント拡張スタックトレースセクションをお読みください。

先行研究

このプロジェクトは、Ned Williamsonとの議論に触発されました。彼のsockfuzzerプロジェクトでは、カスタムスケジューラを使用して同期プリミティブで再スケジューリングを行い、順列を探索することで並行バグを探求しました。この並行テストの側面を中心に据えたカンファレンストークのスライドと録画を参照してください。

私のツールは、SKIのアイデアに大きく基づいていますが、SKIは異なる実装を使用しています。メモリアクセスを記録し、TCGモードのパッチ適用済みQEMUを使用してvCPUのスケジューリングを制御し、VMスナップショットを使用して異なる実行順列を探索します。

競合状態に寄与する可能性のあるメモリアクセス(通信ポイント)の発見

SKIの論文で説明されているように、指定されたマルチスレッドテストケースの興味深い実行順列は、すべてのスレッドのメモリアクセスをトレースし、互いに影響を与える可能性のある2つのスレッド間のアクセスのペアを探すことによって発見できます。これは、少なくとも1つが書き込み操作であり、重なり合うメモリ範囲にアクセスする場合を意味します。SKIの論文では、このようなメモリアクセスを通信ポイントと呼んでいます。

これにはメモリアクセスカバレッジを収集するメカニズムが必要です。SKIはQEMUのTCGモードをパッチ適用することでこれを実現しました。私は代わりに、ASANインストルメンテーションを「アウトライン」モード(コンパイラバックエンドフラグasan-instrumentation-with-call-threshold=0、LinuxカーネルではCONFIG_KASAN_OUTLINEで選択)で使用することに依存しています。これはメモリアクセス時にヘルパー関数呼び出しを生成します。カーネルは、ロックの取得/解放イベントなどに関するより高レベルの情報も提供できるため、このデータを収集するのに適切な場所だと考えていますが、現時点では実装していません。カーネルでの実装は、理論的にはVM内ではなく、ベアメタルハードウェアでテストできることも意味します。

Linuxにはすでに基本的なブロックカーネルカバレッジ情報をユーザー空間にフィードするためのメカニズムとしてKCOVがあるため、メモリアクセスに関する情報を記録するために同じメカニズムを使用することにしました。代替案としては、トレースユースケースを指向しており、fentryフックとシステム全体のデータ収集を含むユースケースを指向したより複雑な出力バッファ管理に基づいた関数グラフトレースモードを含むftraceを使用することも考えられます。KCOVを選択したのは、トレースデータのよりシンプルなインメモリ表現(クラッシュしたVMからトレースデータを回復する際に重要になる可能性がある)のため、無効状態ではオーバーヘッドがほぼゼロの実行時有効化されたインストルメンテーションではなく、静的な常に有効なインストルメンテーションを使用するため、そしてftraceよりも高頻度のトレースイベント向けにKCOVが設計されているという印象のためです。

実装の詳細:ASANとTSAN

ASANは通常、後続のメモリアクセス用のヘルパー呼び出しをマージします。メモリアクセスごとに1つのコールバックを受け取るために、カーネルはasan-opt-same-tempバックエンドフラグを使用してこのコンパイラ最適化を明示的に無効にします。

ASANはUAF(Use-After-Free)の特定を目的としているため、スタック上のオブジェクトへの直接アクセスでは、範囲外アクセスの可能性がある場合を除き、ヘルパー呼び出しを発行しません。これは、待機キューのようなスタック上のオブジェクトを伴う一部の競合状態は、これでは検出できない可能性があることを意味します。ASANはデフォルトではグローバルへのアクセスに対してヘルパー呼び出しを発行しませんが、この最適化はasan-opt-globalsバックエンドフラグを使用して無効にできます。

代替案としては、データ競合の検出を目的とし、アクセスアトミシティに関する情報も提供するTSANインストルメンテーションを使用することが考えられます。TSANインストルメンテーションの欠点は、コンパイラがASANとTSANのフックを同時に発行することをサポートしていないことです。そのため、TSANフックを使用してメモリ安全性違反(UAFなど)の検出を機能させるには、カーネルのASAN実装をTSANフックから実行するか、コンパイラを変更する必要があります。

実装の詳細:KCOVとバックグラウンドワーク

一部の競合状態はバックグラウンドワークを伴います。例:

  • ループバックネットワークパケットの受信処理
  • RCUコールバック

KCOVは、一部のサブシステムでバックグラウンドワークのリモートカバレッジをオプションで収集できます。しかし、アップストリームLinuxでは、私にとって興味深いほとんどの種類のバックグラウンドワークはまだこのメカニズムと統合されていません。リモートカバレッジは現在、主にBluetoothやUSBのようなデバイスからのデータを受信するサブシステムをファジングするために使用されています。

カーネルの他の部分でこれを有効にすることは比較的簡単であるはずです。RCUコールバックのためにこれを実行するドラフトパッチがあります。

実行ごとに安定したメモリアクセス識別子:カウント拡張スタックトレース

メモリアクセスの異なる順序をテストするには、テストケース実行間で興味深いメモリアクセスを安定して識別する方法が必要です。データアドレスに基づいてメモリアクセスを識別することは、テストケース実行中に新しく割り当てられたオブジェクト内のデータアドレスの場合、機能しません。また、命令アドレスのみに基づいてメモリアクセスを識別することは、memcpy()やspin_lock()のような関数内のメモリアクセスではうまく機能しません。

SKIはVM状態のスナップショットを使用してこれを解決します。これにより、各実行は同じグローバル状態から開始されます。

私は代わりに、カウント拡張スタックトレースを使用してメモリアクセスを識別します。各スタックトレース要素は、基本的に呼び出し先関数アドレスと、呼び出し元スタックフレームでこの呼び出し先への呼び出しをいくつスキップする必要があるかを示す数値で構成されます。

カウント拡張スタックトレースのセマンティクスの例は、「このスレッドで、__x64_sys_recvfromの2番目の呼び出し、次にその中の__sys_recvfromの最初の呼び出し、次にその中のsock_recvmsgの最初の呼び出し、次にその中のunix_stream_recvmsgの最初の呼び出し、次にその中のunix_stream_read_genericの最初の呼び出し、次にその中の_raw_spin_unlockの2番目の呼び出し、そしてその中の命令アドレスXでの最初のメモリアクセス」のようになります。

これは実行トレース内のポイントを一意に識別し、具体的なデータアドレスに依存せず、トレースの無関係な部分の制御フローの変更に対して比較的安定しています。

これを機能させるには、KCOVが関数エントリ/エグジットイベントに関する情報を提供する必要があります。これにより、ユーザー空間がKCOVカバレッジ出力を解析する際に、コールスタックがどのように変化するかを追跡できます。これをうまく行うには、SanitizerCoverageの一部としてコンパイラサポートが必要です。数ヶ月前にLLVM機能パッチを適用しました(ドキュメントを参照)。これはLLVM 23.1.0リリースに搭載されました。

遅延注入による実行順序の強制

KCOVを介して特定の実行順序を強制するために、ユーザー空間が特定のカウント拡張スタックトレースでのアクション(基本的に待機/ウェイク)を要求できるKCOV_SET_DI ioctlを実装しました(私のカーネルブランチのドキュメントを参照)。各アクションは、ユーザー空間が提供する共有フラグ配列内のインデックスで、フラグNを設定するか、フラグNが設定されるまで待機します。可能なアクションタイプは次のとおりです。

  • DI_STACK_WAKE_PRE: メモリアクセス前にフラグNを設定
  • DI_STACK_WAIT: メモリアクセス前にフラグNが設定されるまでスピン待機
  • DI_STACK_WAKE_POST: メモリアクセス後にフラグNを設定

同じioctlで、ユーザー空間はスピン待機イテレーションの上限も設定します。

さらに、ユーザー空間が同じフラグと直接対話するためのioctlもあります。

このAPIは、遅延注入の2つの異なる使用方法を可能にします:制約スタイルの遅延注入と完全に指定された順序付け。

制約スタイルの遅延注入(AはBより前に発生)

ユーザー空間は、AはBより前に発生するという一連の制約を設定できます。各制約は、同じフラグを操作する異なるスレッドの2つのアクションのペアとして実装されます。

  • 先に発生する必要があるアクセスに対するDI_STACK_WAKE_POST
  • 後に発生する必要があるアクセスに対するDI_STACK_WAIT

このアプローチでは、実行順序は部分的に非決定論的になります。これは、GUIおよびターミナルUIツールが現在実装しているものです。

利点は、単純なケースではある程度直感的であることですが、イベントがどの順序で発生したかをユーザーに近似的に示すためにタイミング情報を記録する必要があり、実行トレースをより複雑にする可能性があります。また、完全に指定された順序付けよりも多くの制約が必要になることが多く、推論するのがより複雑です。

完全に指定された順序付け(コンテキストスイッチスタイル)

ユーザー空間は、イベントが発生する特定の順序を決定できます。これは、実行が一方のコンテキストから他方のコンテキストに転送されるポイントを選択することによって行われます。2つの実行コンテキストを持つ単純なケースでは、スレッドAがシステムコールを実行し始め、スレッドBがフラグをスピン待機することから始まる必要があります。次に、スレッドAがあるカウント拡張スタックトレースに到達すると、スレッドAはDI_STACK_WAKE_PREとDI_STACK_WAITの組み合わせを使用して自身の実行を一時停止し、スレッドBを続行させます。その後、スレッドBも同様に、元に戻すことができます。

これは、自動A-B-A順列テスターに使用したアプローチです。

デモ:自動テスト

以下でさらに背景を説明しますが、まずは玩具の例で2つの派手なデモをご覧ください!

これは、このテストケースで並行するdup(5)とclose(5)呼び出しに対して、自動A-B-A順列テスターを使用した例です。

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int test_fd;
static int dup_res, dup_errno;

void test_setup(void) {
  test_fd = open("/", O_PATH);
}

void test_thread1(void) {
  dup_res = dup(test_fd);
  dup_errno = errno;
}

void test_thread2(void) {
  close(test_fd);
}

void test_end(void) {
  printf("dup(%d) = %d (%s)\n",
      test_fd,
      dup_res,
      dup_res == -1 ? strerror(dup_errno) : "success");
}

これは、dup(5)が5を返す1つの順列を発見します。これは意図通りに動作していますが、やや驚くべき結果かもしれません。

sh-5.3# ./kcov-autorace testcase/demo-dup-vs-close.so
loading kallsyms
RCU state (excluded): base=ffffffff82970100 len=500
loading testcase
initializing kcov
collecting A-B coverage
dup(5) = 6 (success)
testing candidates
dup(5) = -1 (Bad file descriptor)
dup(5) = -1 (Bad file descriptor)
dup(5) = -1 (Bad file descriptor)
dup(5) = 5 (success)
dup(5) = 6 (success)
dup(5) = 6 (success)
dup(5) = 6 (success)
dup(5) = 6 (success)
dup(5) = 6 (success)
dup(5) = 6 (success)
dup(5) = 6 (success)
stats:  injection-failed:0  wait-timeout:7  reordered:4
sh-5.3#

デモ:GUI

そして、同じテストケースでGUIを使用し、dup(7)が7を返す順序を手動で強制する例を以下に示します。

まず、GUIを起動し、ゲストでテストケースを一度実行します。

sh-5.3# ./kcov-vsock-client testcase/demo-dup-vs-close.so
dup(7) = 8 (success)

この時点では、まだ順序制約は強制されていません。dup()とclose()はランダムに競合しています。GUIは実行がどの順序で発生したかを示します。

[画像:GUIの初期状態のスクリーンショット。2つのスレッドからの関数呼び出しグラフが表示されている。]

現在のビューは、両方のスレッドからの関数呼び出しグラフ(スレッド1は黒のインデント、スレッド2は赤のインデント)を示しているだけです。close()システムコールはこの時dup()の後に実行されました。通常の関数は黒で表示されます。インライン関数は緑で表示されますが、通常の関数を呼び出した場合のみ表示されます(「すべてのインライン関数」がチェックされていないため)。

「通信ポイントにフィルタリング」をチェックすると、多くのメモリアクセスが青で表示されます。これらは通信ポイントです(上記で定義したように、簡単に言うと、他のスレッドが書き込む場所からの読み取り、および他のスレッドがアクセスする場所への書き込み。kfree()は書き込み操作としてカウントされます)。各メモリアクセス行には、アクセスタイプ(Read/Write/Free)、データアドレス、アクセスサイズ、およびアクセス前のメモリ値が表示されます。アクセスにカーソルを合わせると、重なり合うすべてのアクセスが黄色でハイライトされます。

[画像:通信ポイントがハイライトされたGUIのスクリーンショット。]

メモリアクセスを左クリックすると、選択されたアクセスと重なり合うメモリアクセスのみを表示するようにフィルタリングされたビューが表示されます。これは、通信ポイントとして識別されなかった読み取り(すべての書き込みが同じスレッドで発生するため)を示すことができることに注意してください。

[画像:特定のメモリアクセスにフィルタリングされたGUIのスクリーンショット。]

関数名を左クリックすると、右側にソースコードビューが表示され、トレースデータが挿入されます。メモリ読み取りによってロードされたデータ値は赤で表示されます(ソース行とコンパイラがアクセスを帰属させる列の下)。データ書き込みも同様に赤い「WRITE」でマークされます。通信ポイントであるメモリアクセスには、オレンジ色で「INTERFERENCE」がプレフィックスとして付けられます。関数呼び出しは青で表示されます。

[画像:ソースコードとトレースデータが表示されたGUIのスクリーンショット。]

呼び出しグラフビューで2つのメモリアクセスを右クリックすることにより、それらのアクセス間に順序制約を作成できます。これにより、カーネルは最初の選択されたアクセスが2番目の選択されたアクセスより前に発生するように試みます。各順序制約は右側に表示され、2つのカウント拡張スタックトレースとして表されます。ここに表示されるカウント拡張スタックトレースにはインライン関数は含まれていません。また、スタックの最後の最下位要素は実際には特定の命令を識別しますが、UIはそれをあまり表示しません。

この場合、__fget_files_rcu()(__fget_files()にインライン化されている)の2番目のファイルディスクリプタテーブルアクセスを、file_close_fd_locked()(file_close_fd()にインライン化されている)のファイルディスクリプタテーブルエントリ削除より前に順序付ける1つの順序制約を作成しました。これにより、ファイルディスクリプタテーブルの検索が、close()によってクリアされる前にdup()で成功裏に行われることが保証されます。

また、file_close_fd()のspin_unlock(&files->file_lock)をalloc_fd()のspin_lock(&files->file_lock)より前に順序付ける別の順序制約を作成しました。これにより、dup()が未使用のエントリを検索する時点で、ファイルディスクリプタテーブルエントリが解放されていることが保証されます。

このビューでは、順序制約が指定されていますが、テストケースはこの指定された順序でまだ実行されていません。

(このビューはfiles_struct::file_lockへのアクセスを表示するようにフィルタリングされています。)

[画像:順序制約が設定されたGUIのスクリーンショット。]

テストケースを再実行すると、次のようになります。

sh-5.3# ./kcov-vsock-client testcase/demo-dup-vs-close.so
dup(7) = 7 (success)

そして、新しいトレースがUIに表示され、順序制約がどのように適用されたかを示す茶色の「DELAY INJECTION」行が挿入されます。

UIはタイミング情報に基づいてイベントの順序を表示することに注意してください。このタイミング情報はメモリアクセスにのみ関連付けられています。メモリアクセス以外のイベントの配置は、それに基づいて推測されます。データアクセスでフィルタリングされたビューでは、関数エントリイベントは、表示される最初の関数エントリ以外のイベントの時点でしか表示されません。たとえば、次のスクリーンショットでは、file_close_fd()がspin_unlock()を呼び出した時点で、最初のスレッドはすでにget_unused_fd_flags()に入っていた可能性がありますが、イベントは逆の順序で表示されています。ただし、メモリアクセスはほぼ正しい順序で表示されるはずです。ただし、イベントが同じクロック値で発生した場合、メモリアクセスの順序が間違っている可能性があり、タイミング情報は実際のアクセス直前に実行されるインストルメンテーションによって記録されるという注意点があります。(代わりに完全に指定された順序付けに基づいてツールを構築すると、このような注意点が回避されます。)

(このビューはファイルディスクリプタテーブルエントリへのアクセスを表示するようにフィルタリングされています。)

[画像:遅延注入が適用された後のGUIのスクリーンショット。]

さらに詳しいドキュメントはGUI内にあります。

実装状況

LLVMについて:必要なパッチはLLVM 23.1.0に搭載されました。

Linuxカーネルについて:必要なパッチはまだアップストリームカーネルには含まれていません。このブログ記事とほぼ同時期に、アップストリームレビューのためにLinuxカーネルパッチシリーズを投稿しています。私のパッチを含むgitブランチもgithubで利用可能です(まだアップストリーム化の準備ができていないパッチがいくつかあります)。このツールをテストしたい場合は、現時点では私のカーネルブランチを使用する必要があります。(ビルド手順については、ツールリポジトリのREADMEを参照してください。)

私のカーネルパッチはクリーンな状態です。ユーザー空間ツールは、特にGUIの実装に関して、もう少しハック的な部分があります。

コマンドラインツールは2つの並行スレッドしか扱えませんが、GUIは追加の実行コンテキストを扱えます(kcov-vsock-clientハーネスを使用:スレッドAによって起動されたバックグラウンドワーク)。

これが他の人にとっても役立つかどうか、そして他の人がこれの上にどのようなツールを構築できるかを聞くのを楽しみにしています!お気軽にご連絡ください(例えば、maccconc-tooling@google.comへのメールで)。

今後の作業

手動ツール用の制約スタイルではなく、完全に指定された順序付けの使用

非自動ツールは現在制約スタイルの遅延注入を使用していますが、上記で説明したように、完全に指定された順序付けには、より決定論的な動作を含むいくつかの利点があります。将来的にはGUI実装を完全に指定された順序付けを使用するように変更するかもしれません。

人間が読めるメモリアクセストレースのための型情報

人間がメモリアクセストレースを読むために、アクセスされているオブジェクトの型に関する情報を提供することが役立つかもしれません。これを行う1つの方法は、MicrosoftのデバッグツールがCodeViewデバッグ情報でできることに従い、デバッグ情報を使用してメモリ割り当て関数呼び出しサイトを型情報に関連付け、その後アロケータがオブジェクトが割り当てられた呼び出しサイトを追跡できるようにすることです。

私はDWARF標準にそのような機能を追加することを提案しました。これは受け入れられ、現在のDWARF 6ドラフトに含まれています(DW_AT_alloc_typeを検索)。そして、LLVMに十分なサポートを追加しました。これは、CodeViewで既に機能していたのと同じケースで機能しますが、現時点ではC++ new呼び出しにのみ機能し、mallocで機能するために必要な変更はまだ適用されていません。

カーネルでこれを機能させるには、すべてのメモリアクセスレコードに対してアロケータメタデータをクエリするか、システム全体のヒープアロケータメタデータの初期スナップショットと後続のメモリ割り当てに関するメタデータを提供するインフラストラクチャが必要になります。

より高レベルのメモリアクセスフィードバック

現在のプロトタイプの非効率性の1つは、ユーザー空間がロック操作のセマンティクスに関する情報を受け取らないことです。2つのスレッドが、オブジェクトを保護するロックを保持しながら、オブジェクトに対する多数のメモリアクセスを実行する場合、これは多数の潜在的な通信ポイントを生成しますが、実際にはロックされたセクションは1つの大きな通信ポイントを表します。カーネルが「ロック取得」および「ロック解放直前」イベントを提供すると役立つかもしれません。

しかし、それは非常に一般的なアプローチではないかもしれません。なぜなら、ロックによって引き起こされる不可能な順序付けは、オブジェクトがグローバルポインタに公開される前に初期化されているなどの理由による不可能な順序付けと、それほど違いがないからです。

不可能な順序付けのより高速な検出:デッドロック検出

現在の実装では、遅延注入によって不可能な順序付けを強制しようとすると、一方のスレッドがロックでスピン/待機し、もう一方のスレッドが遅延注入タイムアウトに達する結果となり、非効率的です。単純なケースでこのような半デッドロックを検出し、テストケースをより迅速に中止できるロックデバッグインフラストラクチャとの統合が役立つかもしれません。

ファジング:Snowboardのような潜在的な通信ポイントを持つテストケースの構築

Snowboard(ファザー生成の単一スレッドテストケース間の相互作用によって引き起こされる並行バグを探すプロジェクト)は、単一スレッドテストケースでのメモリアクセスの記録情報を使用して、並列実行時にどのテストケースに興味深い通信ポイントがあるかを特定しました。このKCOVベースのインストルメンテーションの上に同様のものを構築することは興味深いでしょう。

単一スレッドテストケースの作成にもこれを使用するのは興味深いかもしれません。まず個々のシステムコールのメモリアクセスカバレッジを収集し、それを使用してどのシステムコールが順次実行された場合に互いに興味深い方法で相互作用する可能性があるかを判断し、このようにしてより長いシステムコールシーケンスを構築します。

VMスナップショット(SKIなど)を使用すると、これは容易になります。なぜなら、私のアプローチではテストケース実行間で安定したデータアドレスが得られないからです。しかし、テストケース実行ごとに割り当てられるすべてのオブジェクトに対して割り当てサイト情報が利用可能であれば、割り当てサイトに基づいて、テストケース間で異なるメモリ位置を一意に識別することによって、おそらく可能でしょう。

KCOV出力をホスト共有メモリへ

現在のツールでは、テスト中のカーネルがパニックした場合にKCOV出力を失うため、カーネルクラッシュ時に何が起こったかを表示するために使用できません。

テスト中のカーネルがKVMゲストであるユースケースでは、ホストがKCOV出力バッファに直接アクセスできるようにすると役立つかもしれません。これを行う1つの方法は、virtiofsとDAX上のファイル内のページをKCOV出力バッファとして使用し、ユーザー空間が提供するページにKCOV出力を書き込めるようにすることです。