夏ゼミ 2022
提案手法の概要
-
目的: データの保護
- Buffer overflowの検知
- 未初期化変数の使用
- Use after freeの検知
- etc
-
特徴(目標):
-
構造体のメンバ変数を保護
→ 高精度な保護 -
選択的データ保護
→ 高効率な保護
-
構造体のメンバ変数を保護
保護手法: Data-Flow Integrity
-
保護のアイデア: プログラム実行中に以下を監視
- データが正しく書き込まれる
- データが正しく読み込まれる
-
保護の流れ
- Use-Def解析: 正しいデータの読み書きの関係を求める
- チェック関数の挿入
- 実行時チェック
例)
1
2
3
4
5
6
7
8
struct Vec2D { int x, y; };
struct Vec2D v;
v.x = 100;
v.y = 200;
v.x;
v.y;
Step.1 Use-Def解析
正しい定義と使用の関係を求める1
2
3
4
5
6
7
8
struct Vec2D { int x, y; };
struct Vec2D v;
v.x = 100;
v.y = 200;
v.x;
v.y;
Use-Def解析の結果
Use | Def | |
---|---|---|
v.x | L.4 | L.7 |
v.y | L.5 | L.8 |
Step.2 チェック関数の挿入
Step.1の結果に沿ったデータの読み書きが行われるかをチェックする関数を挿入set_id()
: データの書き込み情報を取得 + メタデータに保存check_id()
: メタデータから正しい読み込みかどうか確認
1
2
3
4
5
6
7
8
9
10
11
12
struct Vec2D { int x, y; };
struct Vec2D v;
v.x = 100;
set_id(&v.x, ID1);
v.y = 200;
set_id(&v.y, ID2);
check_id(&v.x, ID1);
v.x;
check_id(&v.y, ID2);
v.y;
Step.3 実行時チェック
プログラムを実行し,正しく読み書きされているかどうかをチェック1
2
3
4
5
6
7
8
9
10
11
12
struct Vec2D { int x, y; };
struct Vec2D v;
v.x = 100;
set_id(&v.x, ID1);
v.y = 200;
set_id(&v.y, ID2);
check_id(&v.x, ID1);
v.x;
check_id(&v.y, ID2);
v.y;
上記の例でのポイント: メンバ変数に対して,別々のIDを付与している
→ メンバ変数を区別して保護可能
Field-sensitive DFIの問題点
問題点: メタデータの粒度を細かくしなければならない
Field-in sensitive DFIの場合
"複数バイト ↔ 1 定義ID" の粒度で定義IDを付与
例) DFI: 4 byte ↔ 1 定義ID
struct Aligned {
int x, y;
};
struct Unaligned {
char c1, c2, c3, c4;
};
- メリット: 1つの定義IDで,複数バイトの安全性チェックが可能 → 効率的な保護
- デメリット: 複数のデータに対して,一つの定義IDを付与する → 保護精度が低い
Field-sensitive DFIの場合
"1 byte ↔ 1 定義ID" の粒度で定義IDを付与
- メリット: 1つのデータに対して,少なくとも1つのメタデータを付与 → 保護精度が高い
-
デメリット: 1つのデータの安全性チェックに,複数の定義IDチェックが必要 → 非効率な保護
- 例) aligned.x ↔ 4 定義ID が付与されてしまう
解決案
解決案1: 4-byte Alignment計装 (中間発表以前)
全データを4 byte境界に配置するように計装
結果: 外部ライブラリとのリンク時にエラー多発 → Compatibilityが低い
∵ 構造体のメモリレイアウトを変更してしまうため
解決案2: 2つの領域への割り振り (現在取り組み中)
データを
- 4-byte境界に配置されているデータ
- それ以外
- 4-byte Aligned Region: "4-byte ↔ 1 定義ID" の粒度でメタデータを持つ領域
- Unaligned Region: "1-byte ↔ 1 定義ID" の粒度でメタデータを持つ領域
結果予想:
- 4-byte境界に配置されているデータ: "4-byte ↔ 1 定義ID" → 効率的な保護
- それ以外: "1-byte ↔ 1 定義ID" → 高精度な保護
手法の流れ + 実装
手法概要- 保護データの指定
- 2領域への割り振り
- 各領域毎のデータフロー保護
Step.1 保護データの指定
保護データを以下で指定:
- グローバル変数, ローカル変数: アノテーションを追加
- ヒープ変数: mallocをsafe_mallocに書き換え
1
2
3
4
5
6
7
8
9
10
// global
int g __attribute__((annotate("dfi_protection")));
int main(void) {
// local
int l __attribute__((annotate("dfi_protection")));
// heap
int *p = (int *)safe_malloc(sizeof(int));
}
Step.2 2領域への割り振り
保護データを4-byte境界に配置されているかどうかで,以下の2領域に割り振る:
- 4-byte Aligned Region
- Unaligned Region
ローカル変数の割り振り
ローカル変数のメモリ確保を領域毎のメモリ確保に置き換え
例)
割り振り前
1
2
3
4
5
int main(void) {
struct Aligned aligned __attribute__((annotate("dfi_protection")));
struct Unaligned unaligned __attribute__((annotate("dfi_protection")));
...
}
割り振り後
1
2
3
4
5
6
7
8
9
int main(void) {
struct Aligned *aligned
= (struct Aligned *)aligned_malloc(sizeof(struct Aligned));
struct Unaligned *unaligned
= (struct Unaligned *)unaligned_malloc(sizeof(struct Unaligned));
...
free(aligned);
free(unaligned);
}
ヒープ変数の割り振り
safe_malloc()を領域毎のメモリ確保に置き換え
例)
割り振り前
1
2
3
4
5
6
7
int main(void) {
struct Aligned *aligned
= (struct Aligned *)safe_malloc(sizeof(struct Aligned));
struct Unaligned *unaligned
= (struct Unaligned *)safe_malloc(sizeof(struct Unaligned));
...
}
割り振り後
1
2
3
4
5
6
7
int main(void) {
struct Aligned *aligned
= (struct Aligned *)aligned_malloc(sizeof(struct Aligned));
struct Unaligned *unaligned
= (struct Unaligned *)unaligned_malloc(sizeof(struct Unaligned));
...
}
グローバル変数の割り振り
リンカースクリプトによるセクションの追加 + グローバル変数のセクション指定
例)
割り振り前
1
2
3
// global
struct Aligned aligned __attribute__((annotate("dfi_protection")));
struct Unaligned unaligned __attribute__((annotate("dfi_protection")));
割り振り後
1
2
3
// global
struct Aligned aligned, section "aligned_global";
struct Unaligned unaligned, section "unaligned_global";
Step.3 各領域毎のデータフロー保護
各領域に用意されたメタデータを用いてデータフロー保護
4-byte Aligned Region
4-byte ↔ 1 定義ID の粒度で保護
例)1
2
3
4
5
aligned->x = 100;
aligned_set_id(&aligned->x, ID1); // inserted
...
aligned_check_id(&aligned->x, ID1); // inserted
aligned->x;
Unaligned Region
1-byte ↔ 1 定義ID の粒度で保護
例)1
2
3
4
5
unaligned->c1 = 'a';
unaligned_set_id(&unaligned->c1, ID2); // inserted
...
unaligned_check_id(&unaligned->c1, ID2); // inserted
unaligned->c1;
非保護領域
保護データ領域にアクセスしないようにチェック
例)l.5: arr[i];
にて,保護データを不正に読み込まないようチェック
1
2
3
4
5
int i, arr[100];
...
if (AddrIsInSafeRegion(&arr[i])) // inserted
report_error(&arr[i]); // inserted
arr[i];
実装進捗
- Step.1 保護データの指定 ✓
- Step.2 2領域への割り振り ✓
-
Step.3 各領域毎のデータフロー保護
- 4-byte Aligned Region
- Unaligned Region
- 非保護領域
今後の予定
-
領域毎のデータフロー保護 実装
- 4-byte Aligned Region
- Unaligned Region
- 非保護領域
-
リアル検体解析
- オーバーヘッド計測: SPEC CPU2006
-
攻撃検知:
-
mbed TLS: CVE-2015-5291
- cf. DataShield[CCS'17]
- Sub-object overflow bugを含む検体
- Use-After-Freeを含む検体
-
mbed TLS: CVE-2015-5291
-
実験結果より,今後の方針を決定
-
Sandboxingによるデータ隔離
- 目的: 非保護データが保護データを破壊しないよう保証
- メリット: より効率的な保護が可能
- cf. DataShield[CCS'17]
-
スレッドインターリーブを考慮したフロー保護
- 目的: Inter-thread value flow bugの検知
- メリット: スレッド安全性も保証できそう
- cf. Canary[PLDI'21]
-
Sandboxingによるデータ隔離