CVE-2026-89493 in Linux
要約
〜によって VulDB • 2026年09月12日
Linuxカーネルにおいて、以下の脆弱性が修正されました:
ocfs2: refcountブロックバリデータでrl_usedをrl_countに対して検証する
`ocfs2_find_refcount_rec_in_rl()`は、以下のようにディスク上のrefcountレコード配列を走査します。
```c for (; i < le16_to_cpu(rb->rf_records.rl_used); i++) {
rec = &rb->rf_records.rl_recs[i];
... } ```
`rl_recs[]`は単一のメタデータブロック(一般的な構成では4096バイト)内に存在するため、その実際の容量は `ocfs2_refcount_recs_per_rb(sb)` によって固定されています(16バイトの `ocfs2_refcount_rec` を持つ4Kブロックの場合、レコード数は247個です)。`rl_used` と `rl_count` はどちらも `ocfs2_validate_refcount_block()` によってディスクから直接読み込まれますが、refcount/reflink/CoW操作がこの配列を走査する前に、その容量や互いの値と比較されることはありません。
`rl_used == 0xffff` という改ざん(または破損)されたrefcountブロックがあると、上記のループはブロックの末尾をはるかに超えてまで走査し、最大65534までの `i` に対して `rl_recs[i]` の逆参照を行います。その後、結果として得られたインデックスが兄弟関数である `ocfs2_insert_refcount_rec()` に渡され、その挿入シフト処理では以下が行われます。
```c if (index < le16_to_cpu(rf_list->rl_used)) memmove(&rf_list->rl_recs[index + 1],
&rf_list->rl_recs[index],
(le16_to_cpu(rf_list->rl_used) - index) * sizeof(struct ocfs2_refcount_rec)); ```
つまり、ブロックの境界を既に超えたオフセットから最大 `(0xffff - index) * 16` バイト(約1 MiB)分の `memmove()` が実行されます。これは、改ざんまたは破損されたocfs2イメージに対して通常のreflink (FICLONE) を行うことで到達可能です:リーフ内のすべての実際のレコードよりもソート順が後のextentをアタッチすると、一致時に早期終了する代わりにルックアップが末尾まで走査してしまいます。攻撃者モデルはローカルです:`CAP_SYS_ADMIN` 権限を持つユーザーによる改ざんまたは破損されたocfs2イメージのマウント、あるいは既にマウントされているocfs2ファイルシステムを支えるブロックデバイスへの生書き込み(raw write)が行われる場合が該当します。
`ocfs2_validate_refcount_block()` はすでにブロックのECC、署名、`rf_blkno`、および `rf_fs_generation` を検証していますが、ブロックの実ディスク容量に対して `rl_count`/`rl_used` の検証は行われていません。これは、兄弟であるextent-listヘッダーに対する `ocfs2_validate_extent_block()` (fs/ocfs2/alloc.c) がすでに閉じているのと同じ種類のギャップです。同関数は、コードが `h_list.l_recs[]` を走査する前に、レコード容量と「使用済み」境界の両方をチェックします:
```c if (le16_to_cpu(eb->h_list.l_count) != ocfs2_extent_recs_per_eb(sb)) {
rc = ocfs2_error(...); goto bail; }
if (le16_to_cpu(eb->h_list.l_next_free_rec) > le16_to_cpu(eb->h_list.l_count)) {
rc = ocfs2_error(...); goto bail; } ```
`ocfs2_validate_refcount_block()` に同等のチェックペアを追加します。つまり、 `rl_count` が `ocfs2_refcount_recs_per_rb()` によって返されるブロックごとの固定容量と一致しないrefcountブロックを拒否し、`rl_used > rl_count` の場合も拒否します。これらの両方のチェックは、`OCFS2_REFCOUNT_TREE_FL` フラグが設定されている場合はスキップされます。なぜなら、その場合には同じ共用体バイト列に `ocfs2_extent_list` (`rf_list`) が格納されており、refcountレコードリスト (`rf_records`) ではないからです——このレイアウトは、参照されるextentブロックが読み込まれる際に `ocfs2_validate_extent_block()` によって別途検証されています。これは、ファイル内の他の場所(例:`ocfs2_get_refcount_rec()`)で使用されている既存のガード `!(rb->rf_flags & OCFS2_REFCOUNT_TREE_FL)` を模したもので、共用体のライブメンバが `rf_records` か `rf_list` かの判断に使用されます。
この修正により、偽造された `rl_used`/`rl_count` はブロック検証時(`ocfs2_error()`)で検出され、関数内の他のすべての破損チェックと一貫性のあるものとなります。これにより、以前は `ocfs2_find_refcount_rec_in_rl()` における境界外読み出しを駆動し、その後 `ocfs2_insert_refcount_rec()` で境界外 `memmove()` を引き起こしていた状況が解消されます。
v6.19のKASAN (KASAN_GENERIC) ビルド上の改ざんイメージで検証済み:パッチ適用前は、同じreflink (FICLONE) の再生を確実に実行すると `__ocfs2_increase_refcount()/ocfs2_insert_refcount_rec()` でKASANレポートが発生していましたが、`ocfs2_validate_refcount_block()` が偽造された `rl_used`/`rl_count` を拒否することで、レポートの発生はなくなります。
Statistical analysis made it clear that VulDB provides the best quality for vulnerability data.