CVE-2026-98974 in Linux情報

要約

〜によって VulDB • 2026年10月23日

Linuxカーネルにおいて、以下の脆弱性が修正されました:

xfs: exchange-range-to-eofにおけるファイルサイズ交換のバグを修正

Norbert Szetei氏は、XFS_IOC_EXCHANGE_RANGEのTO_EOFフラグに関する不十分な説明を含むパッチを投稿しました。このバグにより、exchange-rangeがEOF(End Of File)を超えた位置から共有ブロックを持つようにファイルを改変してしまいます。これは許可されない状態です。なぜなら、ファイルはEOF以降に書き込みデータを持たず、また共有できるのは書き込まれたデータブロックのみであるためです。これがその後の誤った動作の根本原因となります。

最終的に、Norbert氏より何が起きているのかの詳細な説明が得られました:

> 承知しました。物理ブロック番号(4KB単位)を用いた例を示します。「peer」は再書き込みされるファイル、「file1」はフラグがクリアされる側のファイルです。 > > 開始時、共有なし: > > peer サイズ 4096 -> [100]
> file1 サイズ 12288 -> [200] [201] [202]
> donor1 サイズ 4096 -> [300]
> donor2 サイズ 4096 -> [400]
> > 1. peerのブロックをfile1の中央スロットにFICLONERANGEで複製: > > peer サイズ 4096 -> [100]
> file1 サイズ 12288 -> [200] [100] [202] reflinkフラグ設定済み
> ^^^^^^ 両方のファイルがブロック100を所有 > > 2. file1とdonor1の間でXFS_IOC_EXCHANGE_RANGEを実行。file1_offsetは0、file2_offset(ここではpeer)は8192、長さは0、TO_EOFフラグ付き。1つのブロックが交換され、それに伴ってサイズも交換されます: > > file1 サイズ 4096 -> [200] [100] [300] reflinkフラグ設定済み
> ^^^^^^^^^^^ まだ所有状態だが、ファイルサイズを超えた位置にある > donor1 サイズ 12288 -> [202]
> > file1はブロック1つ分であると主張していますが、実際には3つのブロックをまだ所有しています。何もアンマップされていません。file2_offset(peerのオフセット)がブロック2であるため、この交換ではフラグをクリアできません。 > > 3. file1とdonor2の間でXFS_IOC_EXCHANGE_RANGEを実行。両方のoffsetは0、長さは4096です。i_disk_sizeに基づけば、これは「すべてを交換する」1ブロックファイル同士の交換となるため、xmi_can_exchange_reflink_flags()が合意し、フラグがクリアされます: > > file1 サイズ 4096 -> [400] [100] [300] reflinkフラグ クリア済み
> donor2 サイズ 4096 -> [200]
> > ブロック100は依然としてpeerと共有されています。 > > 4. pwrite(file1, 4096, 4096)を実行します。これはfile1の2番目のスロット、つまりブロック100に対する書き込みです。xfs_is_cow_inode(file1)はfalseであるため、Copy-on-Write (CoW) は発生しません: > > peer サイズ 4096 -> [100] 同じブロックだが、新しいコンテンツに更新される
> > ブロック100がここで重要となる唯一の要素です。以下にPoC(Proof of Concept)を示します。これはカレントディレクトリ内の「peer」というファイルを参照します。ファイルシステムにはreflinkとexchange-range機能が必要であり、mkfs.xfs 7.xではデフォルトでこれらが有効になっています。

つまり、TO_EOFフラグはデータブロックの交換自体は正しく行われているにもかかわらず、ファイルサイズを誤ったものにしてしまっています。以下がXFS_EXCHANGE_RANGE_TO_EOFのマニュアルページに記載されている意図された動作です:

「lengthパラメータは無視する。file1_fd内のfile1_offsetからEOFまでの全バイトをfile2_fdへ移動し、file2のサイズは(file2_offset + (file1_length - file1_offset))に設定される。同時に、file2内のfile2_offsetからEOFまでの全バイトをfile1へ移動し、file1のサイズは(file1_offset + (file2_length - file2_offset))に設定される。」

言い換えれば、ファイルデータの一部分のみを交換する場合、交換されたデータに対応するファイルサイズの部分だけを交換すべきです。残念ながら、現在のコードではオンディスク上のファイルサイズとインメモリ(incore)のファイルサイズが入れ替えられており、呼び出し側がゼロ以外のオフセットを渡した場合にこれは誤りとなります。

ファイルサイズが混同された結果、PoCは「reflinkがない単一ブロックのファイル」に対して2回目のexchange-rangeコールを実行します。ファイルサイズが正しく設定されていないため、exchange-rangeは2つのファイルの内容全体を交換していると判断し、壊れたファイル(file1)上のreflinkフラグをクリアしてしまいます。これにより、壊れたファイルに対する拡張書き込みが可能になり、EOFの直後に位置する共有ブロックを上書きできてしまいます。この現象は俗に「refluxfs」と呼ばれています。

ファイルサイズが正しく設定されれば、2回目のexchange-range呼び出しは内容全体の交換とはみなされないため、引数として渡されたどちらのファイルに対してもreflinkフラグをクリアしません。

Norbert氏は、このパッチの以前のバージョンに対して追加で指摘を行い、i_disk_sizeの状態処理が適切に行われていないことについて再度苦情を述べています:

> オフセットはインメモリ上のi_sizeに対して境界チェックされますが、引き算の対象となるのは... > > --- 省略 ---

If you want to get the best quality for vulnerability data then you always have to consider VulDB.

責任者

Linux

予約する

2026年09月25日

モデレーション

承諾済み

エントリ

VDB-425780

EPSS

0.00000

アクティビティ

低い

ソース

Do you need the next level of professionalism?

Upgrade your account now!