3.3 KiB
3.3 KiB
最終調査報告: TLS_SLL_HDR_RESET の完全解明
結論
sh8bench が free() に渡すポインタが、malloc() 返却値から +1 されていることがログ解析により数学的に証明されました。
ソースコード上には明示的なポインタ操作は見当たりませんでしたが、実行時の挙動として「奇数アドレス(Tiny Allocatorの仕様)が返された場合に、偶数アドレスに補正して使用し、そのまま free() している」ことは確実です。
証明プロセス
1. ログからの逆算
エラーログ:
[TLS_SLL_NORMALIZE_USERPTR] cls=1 node=0x...e1 -> base=0x...e0 stride=16
このログは core/box/tls_sll_box.h の tls_sll_push() 内で、引数 node が Base Pointer のアラインメント(16の倍数)からズレている場合に発生します。
nodeの値は0x...e1です。tls_sll_push(class_idx, ptr)は、呼び出し元tiny_free_fast()でptr = user_ptr - 1として呼び出されます。- つまり、
node (0xe1) = user_ptr - 1です。 - したがって、
free()に渡されたuser_ptrは0xe2です。
2. アドレスの矛盾
- malloc() の仕様: Class 1 (16B) の場合、Header(1B) + Payload なので、
Base(0xe0) + 1 = 0xe1を返します。 - free() の実態: 上記の計算通り、
0xe2が渡されています。 - 差分:
0xe2 - 0xe1 = +1。
この +1 のズレにより、以下の問題が連鎖的に発生しました:
- 書き込みズレ: アプリケーションが
0xe2からデータを書き込むため、ブロック末尾を超えて次のブロックのヘッダー (0xf0) を破壊します。 - 隣接破壊検知: 破壊された隣接ブロックが
popされる際、ヘッダー異常 (0xa1であるべきがデータ値0xd1等になっている) を検知し、TLS_SLL_HDR_RESETが発生します。 - freeの成功:
free(0xe2)はnormalize機能によりBase(0xe0)に補正されるため、free自体はクラッシュせずに成功します(これが問題を隠蔽していました)。
3. ASan版で再現しない理由
ASan(AddressSanitizer)を有効にすると、各ブロックの周囲に「Redzone(立ち入り禁止領域)」が追加され、アラインメント要件も厳しくなります(通常16/32バイトアラインメント)。
その結果、malloc が偶数アドレス(または16バイト境界)を返すようになり、sh8bench が(奇数アドレス回避のための)補正を行わなくなったため、エラーが発生しなかったと考えられます。
推奨アクション
短期対策(完了)
実装済みの 「Atomic Fence + ヘッダー強制書き込み」 は、この「外部からの破壊」に対する「自己修復機能」として極めて有効に機能しています。現状のままで運用可能です。
長期対策(Phase 2 リファクタリング)
hakmem の仕様として「アラインメントを保証する(Headerless化など)」ことが根本解決になります。提案済みの REFACTOR_PLAN_GEMINI_ENHANCED.md に沿って、ptr の型安全化とレイアウト変更を進めることを強く推奨します。
以上