Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
katagaitai勉強会_20250316_CONFIG_SLAB_VIRTUALの中身見る枠
Search
rmyc
March 17, 2025
Technology
220
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
katagaitai勉強会_20250316_CONFIG_SLAB_VIRTUALの中身見る枠
rmyc
March 17, 2025
Other Decks in Technology
See All in Technology
ストレスなくAIと付き合いたい!こんなときどうしてる?
namiki64
4
610
Metadata - Section 1: HTML Basics
mohab1
0
120
実践!Cloud Run ハンズオン!! / Cloud Run handson
y0hgi
2
300
Active Record Connection Adaptersにおける長年の課題に再び取り組む
yahonda
0
200
エンタメ企業のM&Aを支えるデータ基盤 ― 統合から店舗のリアルタイム分析まで / Data Engineering Summit 2026
genda
0
190
PagerDuty Tech Hub-鳴らすけれど、人は動かない
ezaki
0
180
DGX Spark でどこまでハッキングができるのか?
kash1064
3
1.4k
WasmをRustにする
sansantech
PRO
0
270
PHPカンファレンス関西の招待状 / Invitation to PHP Conference Kansai
ykanoh
0
210
実体験から学ぶ分析エージェントの開発と運⽤
recruitengineers
PRO
8
2.4k
宣言型部分更新は何を解決しどこへ向かうのか
shuta13
1
470
Code for Security プロジェクト紹介 2026年10月
yu23ki14
0
110
Featured
See All Featured
WENDY [Excerpt]
tessaabrams
14
40k
Fireside Chat
paigeccino
43
4.1k
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
600
Building AI with AI
inesmontani
PRO
1
1.3k
Fantastic passwords and where to find them - at NoRuKo
philnash
53
4k
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
2
6.3k
Skip the Path - Find Your Career Trail
mkilby
1
260
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.9k
What does AI have to do with Human Rights?
axbom
PRO
1
2.5k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
56
3.5k
Navigating Team Friction
lara
192
16k
Claude Code のすすめ
schroneko
67
230k
Transcript
CONFIG_SLAB_VIRTUALの中身見る枠 katagaitai勉強会
whoami ⚫ rmyc ⚫ 学生時代は暗号実装系の研究 ⚫ 業務内容 ⚫ マネージドセキュリティサービスの運用・保守 ⚫
メーカリレーション構築とか, サービス製品の新機能検証とか. ⚫ 運用に関わる開発 ⚫ IaCとかCI/CDとか自動化っぽいこと. ⚫ CTFやOffensive Securityっぽい世界とはあまり縁がないかも. ⚫ CTF歴 ⚫ 所属チーム ⚫ katagaitai ⚫ mlab705(たまに) ⚫ 6年目 ⚫ 2019~2020年 Crypto系中心 ⚫ 2020後半からpwn/rev系にシフト ⚫ 2021年~ pwnを中心に. 2025/3/16 2
今日のテーマ ⚫ CONFIG_SLAB_VIRTUALというLinuxカーネルにおける セキュリティパッチの中身を追う ⚫ Cross Cache Attackと呼ばれるKernel Heap Exploitへの対策
2025/3/16 3
テーマの選定理由 ⚫ Cross Cache攻撃の台頭 ⚫ 2022年, DirtyCredというExploit方法が発表される. ⚫ KernelのHeap Exploitテクニックとして脚光を浴び、色々なCTFで出題
されるようになる. ⚫ 2023年後半, 件の対策パッチが発表される. ⚫ カーネルセキュリティ界隈ではちょくちょく言及されている. ⚫ Kernel CTF: mitigation instance ⚫ https://insuyun.github.io/pubs/2023/kim:kernel-ctf-slides.pdf ⚫ ただ, 何をやってるのかよくわからない. (私はわからなかった) ⚫ パッチ自体の解説資料が少ない. ⚫ mainlineに取り込まれてない. ⚫ 2025年2月時点で, 次リリースの6.14でも取り込まれる予定は無し. ⚫ 元ネタのソースコードは以下にforkされて開発されている. ⚫ https://github.com/thejh/linux/tree/slub-virtual-v6.1 2025/3/16 4
発表の流れ ⚫ 大体以下のような流れで説明します. 1. Linux Kernel Heapの復習 2. Cross Cache攻撃の概要
3. CONFIG_SLAB_VIRTUALによる変更点 2025/3/16 5 ※Kernel Pwnをやっている人であれば、1,2は普通に知っていると思いますが, 今回はCTFer以外も参加しているため, 1,2も簡単に説明します. ただし発表時間の都合上, 細かいところは端折ります.
Linux Kernel Heap 2025/3/16 6
◼ カーネル内部で、任意サイズのメモリを動的に確保・解放する仕組み. ◼ 4種類の実装がありカーネルコンパイル時に選択する スラブアロケータ 2025/3/16 7 アロケータ名 (カッコ内は.config) 特徴
バージョン対応 SLOBアロケータ (CONFIG_SLOB=y) K&R mallocを拡張して作成されたコンパクトなアロケータ. v6.2で非推奨 v6.4で廃止 SLABアロケータ (CONFIG_SLAB=y) 初期のLinuxで利用していたアロケータ. Solarisとかでも採用しているらしい. v2.2で導入 v6.5で非推奨 v6.8で廃止 SLUBアロケータ (CONFIG_SLUB=y) デフォルトで選択されるアロケータ. メモリアロケータのセキュリティパッチはほぼこれにしか対応して ない. v2.6で導入 デフォルト SLUB-Tinyアロケータ (CONFIG_SLUB_TINY=y) SLOBの移行先として開発されたアロケータ. 小さいRAMしか積んでない場合に, SLUBよりFootprintが小さくな るらしい. v6.2で導入 今回のテーマであるCONFIG_SLAB_VIRTUALは SLUBにしか対応していないため, SLUBだけを扱います.
スラブオブジェクト ◆ カーネル空間のHeapメモリはオブジェクトを最小単位として管理される. ◆ オブジェクトは2つの状態を保持する. ◆ used → kmalloc/kmem_cache_allocで確保している状態. ◆
freed → kfree/kmem_cache_free済みで解放済みの状態. 2025/3/16 8 freed object in-use object p = (char*)kmalloc(n, GFP_KERNEL); kfree(p);
⚫ オブジェクトを複数並べたもの. ⚫ objectのサイズ(+padding)でページを分割し 敷き詰める. ⚫ usedなオブジェクトは, kmalloc/kmem_cache_allocで確保された部分 ⚫ freedなオブジェクト(空きslot)は,
kfree/kmem_cache_freeで解放された部分 スラブ 2025/3/16 9 object(freed) object(Used) object(used) object(used) object(used) object(freed) object(used) object(freed) ページフレーム(4KB)
⚫ オブジェクトを複数並べたもの. ⚫ objectのサイズ(+padding)でページを分割し, 敷き詰める. ⚫ usedなオブジェクトは, kmalloc/kmem_cache_allocで確保された部分 ⚫ freedなオブジェクト(空きslot)は,
kfree/kmem_cache_freeで解放された部分 スラブ 2025/3/16 10 object(freed) object(Used) object(used) object(used) object(used) object(freed) object(used) object(freed) ページフレーム(4KB) • ページを等間隔に分割してオブジェクトを並べるため, 同一ページ上のオブジェクトは大体同じサイズ. • 例えば右の図では, ページを8分割しているため, 1つのobjectで使えるのは~512byte(4KB / 8 slot) • 512byteピッタリでないオブジェクトはpaddingされる. ※後述するkmem_cache構造体が持つobject_sizeとsizeという2つ の値を使って管理する. • 尚、ページを等間隔で分割できないサイズの場合、ページフレーム最下部を 切り捨てるパターンも存在する. • 今回の話ではあまり本質ではないため説明は割愛 padding padding 512byte 512byte 512byte
スラブキャッシュ ◆ スラブはオブジェクトに関するメタデータを保持していない. ◆ 必ずしもオブジェクトと空きslotのサイズは一致しない. ◆ 例: objectの実際のサイズとページ分割したときのサイズ. ◆ 空きslotの管理
◆ 解放済みのメモリを都度探すと効率が悪い. ◆ そのまま使用すると, 頻繁に確保・破棄されるオブジェクトと, 長期間確保され続けるオブジェクトが同じスペース上に管理される. ◆ フラグメンテーションの問題 ◆ セキュリティ上の問題 ◆ スラブを管理するための構造体(スラブキャッシュ)を定義 ◆ kmem_cache構造体 + kmem_cache_cpu構造体 ◆ オブジェクトの型やサイズ毎にスラブを分離することで, 上記の問題に対処. 2025/3/16 11
◼ ページフレーム内のfreedオブジェクトは, kmem_cache_cpuから 参照される. スラブ内で構成されるリンクリスト 2025/3/16 12 kmem_cache_cpu->freelistから 空きslotを管理するための 片方向リンクリストが繋がっている.
kmem_cache_cpu : page freelist object(Used) object(used) object(used) object(used) object(used) ページフレーム
◼ kmem_cache_cpuはper-CPUであり, kmem_cache->cpu_slabと __per_cpu_offsetから参照される. スラブ内で構成されるリンクリスト 2025/3/16 13 kmem_cacheで スラブのオブジェクトサイズ(object_size), 用途(name)を保持する.
同一タイプのスラブは同じスラブキャッシュで管理 kmem_cache_cpu : page freelist object(Used) object(used) object(used) object(used) object(used) ページフレーム __per_cpu_offset cpu[1] : cpu[0] + kmem_cache cpu_slab object_size offset name :
◼ 空きslot内には, Freepointer(図中はFP)と呼ばれる次の空きslotを指 すアドレスが書き込まれている. スラブ内で構成されるリンクリスト 2025/3/16 14 kmem_cache_cpu : page
freelist object(Used) object(used) object(used) object(used) object(used) ページフレーム __per_cpu_offset cpu[1] : cpu[0] + kmem_cache cpu_slab object_size offset name : FP FP FP 各空きslotの先頭アドレスから kmem_cache->offset分加算したアドレスFPが書き込まれる.
◼ 空きslot内には, Freepointer(図中はFP)と呼ばれる次の空きslotを指 すアドレスが書き込まれている. スラブ内で構成されるリンクリスト 2025/3/16 15 kmem_cache_cpu : page
freelist object(Used) object(used) object(used) object(used) object(used) ページフレーム __per_cpu_offset cpu[1] : cpu[0] + kmem_cache cpu_slab object_size offset name : FP FP FP kmem_cache_cpu->freelistからFPを使って 片方向リンクリストを構成し, ページフレーム上の空きslotを管理する. ※ただしCONFIG_SLUB_FREELIST_HARDENEDが有効の場合, FPの値はマングリングされる. NULL
◼ 次回確保要求があった場合, kmem_cache->freelistから空きslotを 辿って返す. スラブ内で構成されるリンクリスト 2025/3/16 16 kmem_cache_cpu : page
freelist object(Used) object(used) object(used) object(used) object(used) object(used) ページフレーム __per_cpu_offset cpu[1] : cpu[0] + kmem_cache cpu_slab object_size offset name : FP FP NULL 空きslotを返した後は, FPで繋がっていた 次の空きslotを辿ってfreelistにつなぎ直す 新たにオブジェクトが確保された
スラブキャッシュの管理 ⚫ スラブキャッシュはスラブのサイズ・名前に応じて複数作成される. ⚫ 各スラブキャッシュは, 双方向のリンクリストで繋がれており, slab_cachesというグローバル変数から参照可能. 2025/3/16 17 kmem_cache
cpu_slab object_size offset name : list slab_caches kmem_cache cpu_slab object_size offset name : list kmem_cache cpu_slab object_size offset name : list ... ...
スラブキャッシュの管理 ⚫ スラブキャッシュはスラブのサイズ・名前に応じて複数作成される. ⚫ 各スラブキャッシュは, 双方向のリンクリストで繋がれており, slab_cachesというグローバル変数から参照可能. 2025/3/16 18 kmem_cache
cpu_slab object_size offset name : list slab_caches kmem_cache cpu_slab object_size offset name : list kmem_cache cpu_slab object_size offset name : list ... ... • kmem_cache->nameにそのスラブキャッシュの名前が保持される.(char *) • どのような名前のスラブが存在しているかは, cat /proc/slabinfoで見ることができます. • 以降, 同一型オブジェクトの確保・解放はその型のkmem_cacheを経由して行われる. "p9_req_t" "kmem_cache" "kmalloc-512"
Cross Cache Attack 2025/3/16 19
スラブ用ページの解放 ⚫ ページフレーム中のオブジェクトが全て解放されると, スラブとして使用して いたページフレームを, ページアロケータに返却する. ⚫ スラブオブジェクトの型情報は失われ, 他の用途に再利用される. 2025/3/16
20 ページアロケータにページを返却したことでkmalloc-512として確保していたページが他の用途にも再利用されるようになる. kmem_cache (for kmalloc-512) page_allocator object(Freed) object(Freed) object(Freed) object(Freed) object(Freed) object(Freed) object(Freed) object(Freed) 返却されたページはLIFO なリストで管理する
スラブ用ページの再利用 ⚫ ページフレーム中のオブジェクトが全て解放されると, スラブとして使用して いたページフレームを, ページアロケータに返却する. ⚫ スラブオブジェクトの型情報は失われ, 他の用途に再利用される. 2025/3/16
21 kmem_cache (for kmalloc-512) page_allocator object(Freed) object(Freed) object(Freed) object(Freed) object(Freed) object(Freed) object(Freed) object(Freed) kmem_cache (for kmalloc-cg-1k) object(Freed) object(Freed) object(Freed) object(Freed) 返却されたページはLIFO なリストで管理する
スラブ用ページの再利用 ⚫ ページフレーム中のオブジェクトが全て解放されると, スラブとして使用して いたページフレームを, ページアロケータに返却する. ⚫ スラブオブジェクトの型情報は失われ, 他の用途に再利用される. 2025/3/16
22 kmem_cache (for kmalloc-512) page_allocator object(Freed) object(Freed) object(Freed) object(Freed) object(Freed) object(Freed) object(Freed) object(Freed) kmem_cache (for kmalloc-cg-1k) object(Freed) object(Freed) object(Freed) object(Freed) 返却されたページはLIFO なリストで管理する このときページは再利用されるため, ページの仮想アドレス(virt)は同じもの. kmalloc-cg-1kの確保要求によりページアロケータから, さっきまでkmalloc-512用として使っていたページが再利用される. ポイントは, ページアロケータをまたぐことでスラブのタイプによる隔離をバイパスできるというのが重要. ※別にkmalloc-cg-1kでなくてもよい. 要はさっきまで使用していたスラブの型とは異なるスラブになっているということ.
Cross Cache Attack ⚫ 以下のようなスラブの状態を考える. 2025/3/16 23 kmem_cache cpu_slab "kmalloc-512"
: list : page kmem_cache_cpu freelist __per_cpu_offset : cpu[0] + slot object(Used) slot object(Used) slot object(Used) object(Used) object(Used) kmem_cache cpu_slab "kmalloc-cg-1k" : list : : NULL vuln_ptr
Cross Cache Attack ⚫ 以下のようなスラブの状態を考える. 2025/3/16 24 kmem_cache cpu_slab "kmalloc-512"
: list : page kmem_cache_cpu freelist __per_cpu_offset : cpu[0] + slot object(Used) slot object(Used) slot object(Used) object(Used) object(Used) kmem_cache cpu_slab "kmalloc-cg-1k" : list : : NULL 前提条件① kmalloc-512、kmalloc-cg-1kで 任意の回数スラブを確保・解放できる. ※その時点で確保済みなものも, 解放できるとする. 前提条件② kmalloc-512にUAF可能なポインタがある. (あるいはそのようなポインタを作れる.) ここではvuln_ptrとし, 現在確保中だとする. vuln_ptr
Cross Cache Attack ⚫ 同一スラブの確保要求を行い, スラブの空きSlotを使い切る 2025/3/16 25 kmem_cache cpu_slab
"kmalloc-512" : list : page kmem_cache_cpu freelist __per_cpu_offset : cpu[0] + object(Used) object(Used) object(Used) object(Used) object(used) object(Used) object(Used) object(Used) kmem_cache cpu_slab "kmalloc-cg-1k" : list : : 前提条件①を使い, kmalloc-512で確保し, kmalloc-512のスラブを全て埋める. vuln_ptr ptr_2 ptr_1 ptr_3
Cross Cache Attack ⚫ 該当スラブの全オブジェクトを解放する 2025/3/16 26 kmem_cache cpu_slab "kmalloc-512"
: list : page kmem_cache_cpu freelist __per_cpu_offset : cpu[0] + kmem_cache cpu_slab "kmalloc-cg-1k" : list : : kmalloc-512のスラブオブジェクトを全て解放する. vuln_ptr ptr_2 slot slot(victim) slot slot slot slot slot slot ptr_1 ptr_3 前提条件②より, vuln_ptrはUAF可能であり 解放後もvictim slotを指し続けていることに注意
Cross Cache Attack ⚫ 確保済みオブジェクトがなくなり, ページがページアロケータに返る. 2025/3/16 27 kmem_cache cpu_slab
"kmalloc-512" : list : page kmem_cache_cpu freelist __per_cpu_offset : cpu[0] + kmem_cache cpu_slab "kmalloc-cg-1k" : list : : vuln_ptr ptr_2 page frame slot(victim) ptr_1 ptr_3 前提条件②より, vuln_ptrはUAF可能であり 解放後もvictim slotを指し続けていることに注意 kmalloc-512のスラブキャッシュから外され, ページアロケータに返却される. その際, kmalloc-512として使用されていたときの型情報は失われる.
Cross Cache Attack ⚫ 先ほどとは異なるスラブ(ex. kmalloc-cg-1k)で確保する. 2025/3/16 28 kmem_cache cpu_slab
"kmalloc-512" : list : page kmem_cache_cpu' freelist __per_cpu_offset : cpu[0] + kmem_cache cpu_slab "kmalloc-cg-1k" : list : : vuln_ptr ptr_2 slot(victim) ptr_1 ptr_3 vuln_ptrはkmalloc-cg-1kとして 確保されたメモリを指していることになる 大量にkmalloc-cg-1kの確保を要求することで, kmalloc-512から返されたページをkmalloc-cg-1kとして 再利用することができる. slot object(Used) slot object(Used) slot object(Used) object(Used) object(Used) kmem_cache_cpuは先程とは 異なるアドレスで参照される. (cpu_slabの値が異なるため)
Cross Cache Attack ⚫ 先ほどとは異なるスラブ(ex. kmalloc-cg-1k)で確保する. 2025/3/16 29 kmem_cache cpu_slab
"kmalloc-512" : list : page kmem_cache_cpu' freelist __per_cpu_offset : cpu[0] + kmem_cache cpu_slab "kmalloc-cg-1k" : list : : vuln_ptr ptr_2 slot(victim) ptr_1 ptr_3 vuln_ptrはkmalloc-cg-1kとして 確保されたメモリを指していることになる 大量にkmalloc-cg-1kの確保を要求することで, kmalloc-512から返されたページをkmalloc-cg-1kとして 再利用することができる. slot object(Used) slot object(Used) slot object(Used) object(Used) object(Used) kmem_cache_cpuは先程とは 異なるアドレスで参照される. (cpu_slabの値が異なるため) vuln_ptr(kmalloc-512)を使って参照していたポインタから, kmalloc-cg-1kとして使用しているメモリにアクセスできる. →スラブキャッシュを跨いだ(Cross-Cacheな)メモリ破壊が可能
そもそも何がまずいのか? ⚫ スラブアロケータからページアロケータにメモリを返す際に, スラブの情報が失われてしまうことがまずい. ⚫ ページアロケータを経由することで, 型情報で分離していたページが集約 される. 2025/3/16 30
ページフレーム上のオブジェクトを解放し尽くしても, ページアロケータに返さなければよいのでは
CONFIG_SLAB_VIRTUAL 2025/3/16 31
CONFIG_SLAB_VIRTUAL ⚫ スラブアロケータが解放したページをページアロケータに返却しない. ⚫ スラブに使用したページフレームは同一のスラブ内で保持し続ける ⚫ スラブの型情報が失われないためページがCrossCacheしない. 2025/3/16 32
カーネルのメモリマップ(4-Level Paging) 2025/3/16 33 0x0000_0000_0000_0000 ユーザ空間領域 (128TiB) LDTマップ領域(0.5TiB) ストレートマップ領域 (64TiB)
vmalloc/ioremap領域 (32TiB) 仮想メモリマップ(1TiB) カーネルtext領域(512MB) KASAN shadowメモリ (16TiB) esp fixup stacks(0.5TiB) cpu entry area(0.5TiB) ハイパーバイザ用に予約された領域 (8TiB) EFI領域(64GiB) 0x0000_7fff_ffff_ffff 0xffff_8000_0000_0000 0xffff_ffff_8000_0000 0xffff_8880_0000_0000 0xffff_c87f_ffff_ffff 0xffff_ec00_0000_0000 0xffff_fe00_0000_0000 0xffff_8800_0000_0000 vmemmap_base 0xffff_ea00_0000_0000 0xffff_fe00_0000_0000 0xffff_c900_0000_0000 vmalloc_base ストレートマップ領域: page_offset_baseから開始する領域であり, 物理メモリがそのままMappingされる. virtual memory map領域: page構造体の配列領域. ページフレームの管理 カーネルスタック領域: プロセス毎のページテーブルや コンテキスト情報を保持 仮想アドレス上のカーネルのコード領域
カーネルのメモリマップ(4-Level Paging) 2025/3/16 34 0x0000_0000_0000_0000 ユーザ空間領域 (128TiB) LDTマップ領域(0.5TiB) ストレートマップ領域 (64TiB)
vmalloc/ioremap領域 (32TiB) 仮想メモリマップ(1TiB) カーネルtext領域(512MB) KASAN shadowメモリ (16TiB) esp fixup stacks(0.5TiB) cpu entry area(0.5TiB) ハイパーバイザ用に予約された領域 (8TiB) EFI領域(64GiB) 0x0000_7fff_ffff_ffff 0xffff_8000_0000_0000 0xffff_ffff_8000_0000 0xffff_8880_0000_0000 0xffff_c87f_ffff_ffff 0xffff_ec00_0000_0000 0xffff_fe00_0000_0000 0xffff_8800_0000_0000 vmemmap_base 0xffff_ea00_0000_0000 0xffff_fe00_0000_0000 0xffff_c900_0000_0000 vmalloc_base 仮想アドレス上のカーネルのコード領域 有効にすると, 0xffff_fe80_0000_0000 - 0xffff_feff_ffff_ffff (cpu_entry_area領域とesp fixup staksの間)に 新たにSLUB Virtual memory領域を作成. SLUB virtual memory(0.5TiB) ソースコード上の定義 // line 201@arch/x86/include/asm/pgtable_64_types.h #ifdef CONFIG_SLAB_VIRTUAL #define SLAB_PGD_ENTRY _AC(-3, UL) #define SLAB_BASE_ADDR (SLAB_PGD_ENTRY << P4D_SHIFT) #define SLAB_END_ADDR (SLAB_BASE_ADDR + P4D_SIZE) #endif /* CONFIG_SLAB_VIRTUAL */ ストレートマップ領域: page_offset_baseから開始する領域であり, 物理メモリがそのままMappingされる. カーネルスタック領域: プロセス毎のページテーブルや コンテキスト情報を保持 virtual memory map領域: page構造体の配列領域. ページフレームの管理
Slab Virtual Memory領域の作成 ⚫ 更に2つの領域に分割 ⚫ slab_meta領域 ⚫ slab_data領域 2025/3/16
35 slab virtual area slab_data領域 slab_meta領域
Slab Virtual Memory領域の作成 ⚫ 更に2つの領域に分割 ⚫ slab_meta領域 ⚫ slab_data領域 2025/3/16
36 slab virtual area slab_data領域 slab_meta領域 slab_meta領域をslab構造体の配列として使用する slab_data/metaは固定で領域を占有するのではなく, メモリの確保状況に応じて上下することに注意.
変更点: kmem_cache構造体 ⚫ kmem_cache構造体に2つのリストを追加 ⚫ freed_slab_normal/freed_slab_minメンバ ⚫ これが実質的な空きページ管理用のfreelist ⚫ freed_slab_normalに空きページ(slab構造体)をつなぐ.
2025/3/16 37 slab_caches kmem_cache cpu_slab object_size offset name : list freed_slab_min freed_slab_normal : slab_list pinstate slab flush_list_elem backing_folio : ※ここでは紙面の都合上, 関係無いメンバは削っている. : slab_list pinstate slab flush_list_elem backing_folio : 尚, この絵では使っていないが, slab構造体にもflush_list_elemという list_headなメンバが追加されている. (後で使う)
SLUBアロケータ→ページアロケータ ⚫ kmallocによりメモリ確保を行う場合の実行パス. 2025/3/16 38 kmalloc() → kmalloc_trace() → __kmem_cache_alloc_node()
→ slab_alloc_node() → __slab_alloc() → ___slab_alloc() → new_slab() → allocate_slab() → alloc_slab_page() ★slabに使用するページが枯渇し → alloc_pages() ページアロケータに新たな確保要求. → __alloc_pages()★Page AllocatorのAPI ※CONFIG_NUMA=yの場合, memory policyに応じてalloc_pages()は内部で alloc_page_interleave() or alloc_pages_preferred_many()を呼ぶが, その先で結局__alloc_pages()を呼ぶ. CONFIG_SLAB_VIRTUAL=nの場合
確保要求の実行パスの違い ⚫ kmallocによりメモリ確保を行う場合の実行パス. 2025/3/16 39 kmalloc() → kmalloc_trace() → __kmem_cache_alloc_node()
→ slab_alloc_node() → __slab_alloc() → ___slab_alloc() → new_slab() → allocate_slab() → alloc_slab_page() ★slabに使用するページが枯渇し → alloc_pages() ページアロケータに新たな確保要求. → __alloc_pages()★Page AllocatorのAPI ※CONFIG_NUMA=yの場合, memory policyに応じてalloc_pages()は内部で alloc_page_interleave() or alloc_pages_preferred_many()を呼ぶが, その先で結局__alloc_pages()を呼ぶ. CONFIG_SLAB_VIRTUAL=nの場合 kmalloc() → kmalloc_trace() → __kmem_cache_alloc_node() → slab_alloc_node() → __slab_alloc() → ___slab_alloc() → new_slab() → allocate_slab() → alloc_slab_pv_page()★ここが違う. CONFIG_SLAB_VIRTUAL=yの場合 allocate_slab()以降の呼び出しが異なる. 後述するがalloc_slab_pv_page()は__alloc_pages()に到達しない. = ページアロケータからメモリを確保しない.
確保要求の実行パスの違い ⚫ kmallocによりメモリ確保を行う場合の実行パス. 2025/3/16 40 kmalloc() → kmalloc_trace() → __kmem_cache_alloc_node()
→ slab_alloc_node() → __slab_alloc() → ___slab_alloc() → new_slab() → allocate_slab() → alloc_slab_page()★slabに使用するページが枯渇し → alloc_pages()※★ページアロケータに新たな確保要求. → __alloc_pages()★Page AllocatorのAPI ※CONFIG_NUMA=yの場合, memory policyに応じてalloc_pages()は内部で alloc_page_interleave() or alloc_pages_preferred_many()を呼ぶが, その先で結局__alloc_pages()を呼ぶ. CONFIG_SLAB_VIRTUAL=nの場合 kmalloc() → kmalloc_trace() → __kmem_cache_alloc_node() → slab_alloc_node() → __slab_alloc() → ___slab_alloc() → new_slab() → allocate_slab() → alloc_slab_pv_page()★ここが違う. CONFIG_SLAB_VIRTUAL=yの場合 allocate_slab()以降の呼び出しが異なる. 後述するがalloc_slab_pv_page()は__alloc_pages()に到達しない. = ページアロケータからメモリを確保しない. 本質的に処理が異なるのは, allocate_slab()以降の呼び出し
allocate_slab()を呼び出すパターン ⚫ kmem_cacheのfreelistが指すページのスロットを使い切った状態. ⚫ スラブアロケータは, オブジェクトの空きスロットを追加で 確保するための空きページがほしい. 2025/3/16 41 kmem_cache
cpu_slab object_size offset name : list : page partial kmem_cache_cpu freelist __per_cpu_offset cpu[1] : cpu[0] + object(Used) object(Used) object(Used) object(Used) object(Used) object(Used) object(Used) object(Used) kmem_cache cpu_slab object_size offset name : list kmem_cache_cpu->freelistのページに空きスロットが一つも存在しない. ※補足: 実際はkmem_cache_cpuはpartialというメンバを持っており (CONFIG_SLUB_PARTIAL), そちらには虫食いで(partial) slotが残っているスラブを保持している場合があります.
allocate_slab()の呼び出し ⚫ allocate_slab()の処理は, 呼び出した時点のkmem_cacheの状態に依 存して処理内容が異なる. ⚫ パターンは大きく2つ. ⚫ 該当のスラブキャッシュが空きページを保持している場合 ⚫
スラブキャッシュのリストから確保 ⚫ 空きページを保持していない場合 ⚫ slab用のページをvirtual slab areaに新規作成. 2025/3/16 42
allocate_slab() ⚫ alloc_slab_pv_page()関数の実装 2025/3/16 43 kmem_cache cpu_slab object_size offset name
: list freed_slab_min freed_slab_normal : slab_list pinstate slab flush_list_elem backing_folio : kmem_cache->freed_slab_normalのリストにslabが繋がっている場合, 先頭のslabをunlink. : slab_list pinstate slab flush_list_elem backing_folio : 先頭のslabを外して確保要求に返す
SLUBアロケータ→ページアロケータ ⚫ alloc_slab_pv_page()関数の実装 ⚫ kmem_cache->freed_slab_normalにslabが繋がっていない場合, 新たにslab meta領域を作成する. 2025/3/16 44 kmem_cache
cpu_slab object_size offset name : list freed_slab_min freed_slab_normal slabがリストに繋がっていない alloc_slab_meta()関数を呼び, 新たにslab meta領域を作成する. static struct slab *alloc_slab_meta(unsigned int order, gfp_t gfp_flags)
alloc_slab_meta()によるmeta領域の作成 ⚫ 初期状態 ⚫ グローバル変数slub_addr_baseから現在のslab_data領域の下限を取得. 2025/3/16 45 slab_data領域 slab_meta領域 slub_addr_base
slub_addr_baseは現在使用中のslab_dataの最高位アドレスを指し続けるグローバル変数.
alloc_slab_meta()によるmeta領域の作成 ⚫ 確保要求サイズから新たに追加するdata領域のrangeを決定. ⚫ data領域の開始: ALIGN(slub_addr_base + PAGE_SIZE) ⚫ data領域の終端:
slub_addr_base + 1 << order 2025/3/16 46 slab_data領域 slab_meta領域 slub_addr_base data_range data_range_end: slub_addr_base + 1 << order data_range_start: ALIGN(slub_addr_base + PAGE_SIZE)
alloc_slab_meta()によるmeta領域の作成 ⚫ 追加したdata領域からmeta領域を作成. ⚫ meta_range_start: virt_to_slab(data_range_start) ⚫ meta_range_end: meta_range_start +
STRUCT_SLAB_SIZE * ((1UL << order) - 1); 2025/3/16 47 slub_addr_base data_range_end: slub_addr_base + 1 << order data_range_start: ALIGN(slub_addr_base + PAGE_SIZE) meta_range_start: virt_to_slab(data_range_start) meta_range meta_range_last: meta_range_start + STRUCT_SLAB_SIZE * ((1 << order) - 1) slab_data領域 slab_meta領域 data_range
alloc_slab_meta()によるmeta領域の作成 ⚫ 追加したdata領域からmeta領域を作成. ⚫ meta_range_start: virt_to_slab(data_range_start) ⚫ meta_range_end: meta_range_start +
STRUCT_SLAB_SIZE * ((1UL << order) - 1) 2025/3/16 48 slub_addr_base data_range_end: slub_addr_base + 1 << order data_range_start: ALIGN(slub_addr_base + PAGE_SIZE) meta_range_start: virt_to_slab(data_range_start) meta_range meta_range_last: meta_range_start + STRUCT_SLAB_SIZE * ((1 << order) - 1) slab_data領域 slab_meta領域 data_range STRUCT_SLAB_SIZEとしてマクロ定義されているが, 値はsizeof(struct slab) (== 0x70)ではなく0xc0であることに注意.
alloc_slab_meta()によるmeta領域の作成 ⚫ 追加したdata領域からmeta領域を作成. ⚫ meta_range_start: virt_to_slab(data_range_start) ⚫ meta_range_end: meta_range_start +
0xc0 * ((1UL << order) - 1); 2025/3/16 49 slub_addr_base data_range_end: slub_addr_base + 1 << order data_range_start: ALIGN(slub_addr_base + PAGE_SIZE) meta_range_start: virt_to_slab(data_range_start) meta_range meta_range_last: meta_range_start + 0xc0 * ((1 << order) - 1) slab_data領域 slab_meta領域 data_range CONFIG_SLAB_VIRTUALのvirt_to_slabはいわゆるvirt_to_page/page_to_virtと同じような関数 ただし, 通常とは異なり以下の帯域を指す。 virt: slab_data帯域の仮想アドレス slab: slab_meta領域 ※ストレートマッピング領域や, vmemmap領域のアドレスではない。
【補足】slab_to_virt/slab_to_virt ⚫ CONFIG_SLAB_VIRTUAL有効なカーネルではslab/virtが指す領域が 異なる. 2025/3/16 50 slab_data領域 slab_meta領域 ストレートマッピング領域 vmemmap領域
virt page/slab/folio slab_data領域 slab_meta領域 ストレートマッピング領域 vmemmap領域 page/folio virt slab SLAB_VIRTUAL有効の場合, slab *はvmemmapを指さず, slab_meta領域を指す. 従来のvmemmapはfolio */page *で表現する. CONFIG_SLAB_VIRTUAL=n CONFIG_SLAB_VIRTUAL=y SLAB_VIRTUAL有効の場合, virtはストレートマップ領域を指さず, slab_data領域を指す.
alloc_slab_meta()によるmeta領域の作成 ⚫ 作成したmeta領域をページサイズで分割し, 分割したブロックの先頭 アドレスをカーネルのページテーブルに登録する. 2025/3/16 51 slab_data領域 slab_meta領域 slub_addr_base
data_range data_range_end: slub_addr_base + 1 << order(ページアラインされている) data_range_start: ALIGN(slub_addr_base + PAGE_SIZE) meta_range_start: virt_to_slab(data_range_start) meta_range meta_range_last: meta_range_start + 0xc0 * ((1 << order) - 1) meta_rangeをPAGE_SIZEで分割する. : PAGE_SIZE PAGE_SIZE PAGE_SIZE
meta_rangeをカーネルのページテーブルに登録 2025/3/16 52 meta_rangeをページサイズ毎に分割したアドレスを,カーネルのページテーブルに登録する. : PAGE_SIZE PAGE_SIZE PAGE_SIZE init_mm pgd
init_top_pgt pgd[0] pgd[1] pgd[2] pgd[i] : pmd[0] pmd[1] pmd[2] : pmd[j] : pmd pud[0] pud[1] pud[2] : pud[k] : pud addr_0 addr_1 addr_2 addr_z pte[0] pte[1] pte[2] : pte[l] : pte 今回の確保要求で作成されたmeta領域
スラブの解放: kfree ⚫ CONFIG_SLAB_VIRTUAL=yなカーネルで, kfreeからページを解放 するまでの呼び出しパスは以下の通り. 2025/3/16 53 kfree() →
__kmem_cache_free() → slab_free() → slab_free_freelist_hook() → do_slab_free() → __slab_free() → discard_slab() → free_slab() → rcu_free_slab() → __free_slab() CONFIG_SLAB_VIRTUAL=yの場合 kfree() → __kmem_cache_free() → slab_free() → slab_free_freelist_hook() → do_slab_free() → __slab_free() → discard_slab() → free_slab() → rcu_free_slab() → __free_slab() CONFIG_SLAB_VIRTUAL=nの場合
スラブの解放: kfree ⚫ CONFIG_SLAB_VIRTUAL=yなカーネルで, kfreeからページを解放 するまでの呼び出しパスは以下の通り. 2025/3/16 54 kfree() →
__kmem_cache_free() → slab_free() // NUMA_NO_NODEで呼ぶ. → slab_free_freelist_hook() → do_slab_free() → __slab_free() ★チェックが追加されている. → discard_slab() → free_slab() → rcu_free_slab() → __free_slab() CONFIG_SLAB_VIRTUAL=yの場合 kfree() → __kmem_cache_free() → slab_free() → slab_free_freelist_hook() → do_slab_free() → __slab_free() → discard_slab() → free_slab() → rcu_free_slab() → __free_slab() CONFIG_SLAB_VIRTUAL=nの場合 解放の場合, __free_slabを呼び出すまではほぼ同じ実行パスをたどる.
__free_slabの処理 ⚫ 大きく2つのステップを踏む 1. 該当ページのmeta領域をページテーブルから削除 2. 空きページ回収用のスレッドを登録 ⚫ 発動したら該当ページを解放元のkmem_cacheにつなぐ 2025/3/16
55
__free_slabを呼び出すパターン ⚫ kmem_cache->freelistが指すスラブオブジェクトを全て解放した状 態. 2025/3/16 56 kmem_cache cpu_slab object_size offset
name : list : page partial kmem_cache_cpu freelist __per_cpu_offset cpu[1] : cpu[0] + slab_caches kmem_cache cpu_slab object_size offset name : list kmem_cache cpu_slab object_size offset kmalloc-512 : list ... ... slot slot slot slot slot slot slot slot
meta領域のページテーブルエントリ削除 ⚫ 解放したページをカーネルのページテーブルから削除. ⚫ ページサイズで分割していたmeta領域のアドレスを, それぞれpte_clearする. ⚫ 物理アドレスと仮想アドレスのマッピングが切り離される. 2025/3/16 57
ページサイズ毎に分割したmeta_rangeのアドレスを,カーネルのページテーブルから削除する. : PAGE_SIZE PAGE_SIZE PAGE_SIZE init_mm pgd init_top_pgt pgd[0] pgd[1] pgd[2] pgd[i] : pmd[0] pmd[1] pmd[2] : pmd[j] : pmd pud[0] pud[1] pud[2] : pud[k] : pud addr_0 addr_1 addr_2 addr_z pte[0] pte[1] pte[2] : pte[l] : pte ページサイズ毎に分割したmeta_rangeのアドレス (addr_0 ~ addr_z)のページテーブルエントリを削除
解放したslabページの繋ぎ変え ⚫ 解放したページグローバル変数slub_tlbflush_queueにつなぐ. 2025/3/16 58 slub_tlbflush_queue list_headなグローバル変数
解放したslabページの繋ぎ変え ⚫ 解放したページグローバル変数slub_tlbflush_queueにつなぐ. ⚫ slab->flush_list_elemをリストの先頭につなぐ. 2025/3/16 59 slub_tlbflush_queue slab_cache slab_list
pinstate slab flush_list_elem backing_folio : slab->flush_list_elemをslub_tlbflush_queueにつなぐ. :
解放したslabページの繋ぎ変え ⚫ 解放したページグローバル変数slub_tlbflush_queueにつなぐ. 2025/3/16 60 slub_tlbflush_queue slab_cache slab_list pinstate slab
A flush_list_elem backing_folio : slabは自分を解放したスラブキャッシュへの ポインタを持っている. (これはCONFIG_SLAB_VIRTUALの有効無効に 関わらず存在するメンバ) : kmem_cache : name : list freed_slab_min freed_slab_normal
解放したslabページの繋ぎ変え ⚫ 解放したページグローバル変数slub_tlbflush_queueにつなぐ. 2025/3/16 61 slub_tlbflush_queue slab_cache slab_list pinstate slab
B flush_list_elem backing_folio : 解放した逆順にリンクされる. ※A -> Bと解放するとB -> Aとつながる. : kmem_cache : name : list freed_slab_min freed_slab_normal slab_cache slab_list pinstate slab A flush_list_elem backing_folio : :
解放したslabページの繋ぎ変え ⚫ 回収したページを解放元のkmem_cacheにつなぐ. 2025/3/16 62 slub_tlbflush_queue slab_cache slab_list pinstate slab
B flush_list_elem backing_folio : slub_tlbflush_queueに繋がっている順番にkmem_cacheの freed_slab_normal/freed_slab_minに繋ぎ変える. : kmem_cache : name : list freed_slab_min freed_slab_normal slab_cache slab_list pinstate slab A flush_list_elem backing_folio : :
slub_kworkerによる空きページの回収 ⚫ slub_tlbflush_queueに繋がれた解放ページは, カーネルスレッドで 非同期に回収される. ⚫ slub_tlbflush_kworkerを回収用のworkerスレッドとして登録する. ⚫ workerスレッドは繋がったページをたどり, kmem_cacheの
flush_elem_normal/flush_elem_minにつなぐ. 2025/3/16 63
解放したslabページの繋ぎ変え ⚫ 回収したページを解放元のkmem_cacheにつなぐ. 2025/3/16 64 slub_tlbflush_queue slab_cache slab_list pinstate slab
B flush_list_elem backing_folio : : kmem_cache : name : list freed_slab_min freed_slab_normal slab_cache slab_list pinstate slab A flush_list_elem backing_folio : : 根本に繋がっていたBからkmem_cacheのリストに繋ぎ直される.
解放したslabページの繋ぎ変え ⚫ 回収したページを解放元のkmem_cacheにつなぐ. 2025/3/16 65 slub_tlbflush_queue slab_cache slab_list pinstate slab
A flush_list_elem backing_folio : : kmem_cache : name : list freed_slab_min freed_slab_normal slab_cache slab_list pinstate slab B flush_list_elem backing_folio : : kmem_cacheにつなぎ直す場合は, freed_slab_normalの根本からつな ぎ直されるため, 最終的には解放順でつながる. kmem_cacheから伸びるリストでは, A -> Bの順序で繋がっている.
まとめ ⚫ なぜCross Cacheできないのか? ⚫ スラブアロケータが空きページを返却する際, ページアロケータに返却せ ず, 解放元のスラブキャッシュのリンクリストにつなぐ. ⚫
kmem_cache->freed_slabs_(normal|min) ⚫ そのため, 一度スラブキャッシュに確保されたページは, その後解放して もそのスラブキャッシュ専用にバインドされ続ける. ⚫ 解放時, 該当ページのページテーブルエントリは削除されているため, その後 の確保要求をfreed_slab_normal/minから確保したとしても, そのページは 新しい物理アドレスに紐づいている. ⚫ スラブキャッシュを跨いだページの再利用が実質不可能であるため, Cross Cacheできない. ⚫ ただし, CONFIG_SLAB_VIRTUALが防ぐのはあくまでCross Cache だけ. ⚫ 同一スラブ内でオブジェクトをOverlapする古典的なUAFはこのパッチで は防げないことに注意. 2025/3/16 66
Fin 2025/3/16 67