LinuxのLinux KernelにおけるTime-of-check Time-of-use (TOCTOU) 競合状態の脆弱性
タイトル LinuxのLinux KernelにおけるTime-of-check Time-of-use (TOCTOU) 競合状態の脆弱性
概要

Linuxカーネルにおいて、以下の脆弱性が修正されました。cephの非同期unlink中に発生するi_nlinkアンダーランの問題の修正です。非同期unlinkの間、完了(最終的にi_nlinkを更新する処理)が来る前にi_nlinkカウンターを減少させてしまいます。これは「unlinkが成功すると仮定している」ために起きていました。この方法は悪い考えではありませんが、他のクライアントによる削除(または自身のunlinkの完了)と競合し、以下のようなWARNINGを引き起こすアンダーランとなります。具体的には、WARNING: CPU: 85 PID: 25093 at fs/inode.c:407 drop_nlink+0x50/0x68 という警告が表示されます。ceph_unlink()内ではceph_mdsc_submit_request()がCEPH_MDS_OP_UNLINKのリクエストをMDSに送信しますが、完了を待ちません。この呼び出しと後続のdrop_nlink()呼び出しの間に、ワーカースレッドがCEPH_CAP_OP_IMPORT、CEPH_CAP_OP_GRANT、またはCEPH_MSG_CLIENT_REPLY(自身の完了の可能性あり)を処理する可能性があります。その結果、set_nlink()が呼ばれ、MDSから受け取った値でi_nlinkカウンターが更新されます。もし新しいi_nlinkの値が0の場合、それ以上の減少は違法ですが、ceph_unlink()は減少処理を行ってしまいます。WARNINGは次の手順で再現可能です。1. 非同期unlinkを強制します。非同期経路のみが影響を受けます。Ceph内部の詳細は不明ですが、MDSが「Fxr」権限を与えない理由はわかっていません。そこでget_caps_for_async_unlink()を常に成功するようにパッチしました。(このWARNINGは未パッチのカーネルでも発見されており、理論上のバグではありません。)2. ceph_mdsc_submit_request()の後にsleepを挿入し、unlink完了処理がdrop_nlink()の前にワーカースレッドで処理されるようにします。これによりdrop_nlink()実行前にi_nlinkが既に0となります。解決策としては、カウンターがすでに0の場合は減算をスキップすることですが、ロックなしでは依然として競合状態(TOCTOU問題)が発生します。ceph_fill_inode()とhandle_cap_grant()は共にset_nlink()実行中にceph_inode_info.i_ceph_lockスピンロックを保持しているため、このロックでi_nlinkの更新処理を保護することが適切だと考えられます。類似の先行例としてはNFSやSMB(inode.i_lock使用)、AFS(afs_vnode.cb_lock使用)があり、これらはいずれもゼロチェックを行っています。

想定される影響 当該ソフトウェアが扱う情報について、外部への漏えいは発生しません。 また、当該ソフトウェアが扱う情報について、書き換えは発生しません。 さらに、当該ソフトウェアが完全に停止する可能性があります。 そして、この脆弱性を悪用した攻撃の影響は、他のソフトウェアには及びません。 
対策

リリース情報、またはパッチ情報が公開されています。参考情報を参照して適切な対策を実施してください。

公表日 2026年5月8日0:00
登録日 2026年5月28日14:40
最終更新日 2026年5月28日14:40
CVSS3.0 : 警告
スコア 4.7
ベクター CVSS:3.0/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H
影響を受けるシステム
Linux
Linux Kernel 5.11 以上 5.15.203 未満
Linux Kernel 5.16 以上 6.1.167 未満
Linux Kernel 5.7 以上 5.10.253 未満
Linux Kernel 6.13 以上 6.18.19 未満
Linux Kernel 6.19 以上 6.19.9 未満
Linux Kernel 6.2 以上 6.6.130 未満
Linux Kernel 6.7 以上 6.12.78 未満
Linux Kernel 7.0
CVE (情報セキュリティ 共通脆弱性識別子)
CWE (共通脆弱性タイプ一覧)
その他
変更履歴
No 変更内容 変更日
1 [2026年05月28日]
  掲載
2026年5月28日14:40

NVD脆弱性情報
CVE-2026-43420
概要

In the Linux kernel, the following vulnerability has been resolved:

ceph: fix i_nlink underrun during async unlink

During async unlink, we drop the `i_nlink` counter before we receive
the completion (that will eventually update the `i_nlink`) because "we
assume that the unlink will succeed". That is not a bad idea, but it
races against deletions by other clients (or against the completion of
our own unlink) and can lead to an underrun which emits a WARNING like
this one:

WARNING: CPU: 85 PID: 25093 at fs/inode.c:407 drop_nlink+0x50/0x68
Modules linked in:
CPU: 85 UID: 3221252029 PID: 25093 Comm: php-cgi8.1 Not tainted 6.14.11-cm4all1-ampere #655
Hardware name: Supermicro ARS-110M-NR/R12SPD-A, BIOS 1.1b 10/17/2023
pstate: 60400009 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
pc : drop_nlink+0x50/0x68
lr : ceph_unlink+0x6c4/0x720
sp : ffff80012173bc90
x29: ffff80012173bc90 x28: ffff086d0a45aaf8 x27: ffff0871d0eb5680
x26: ffff087f2a64a718 x25: 0000020000000180 x24: 0000000061c88647
x23: 0000000000000002 x22: ffff07ff9236d800 x21: 0000000000001203
x20: ffff07ff9237b000 x19: ffff088b8296afc0 x18: 00000000f3c93365
x17: 0000000000070000 x16: ffff08faffcbdfe8 x15: ffff08faffcbdfec
x14: 0000000000000000 x13: 45445f65645f3037 x12: 34385f6369706f74
x11: 0000a2653104bb20 x10: ffffd85f26d73290 x9 : ffffd85f25664f94
x8 : 00000000000000c0 x7 : 0000000000000000 x6 : 0000000000000002
x5 : 0000000000000081 x4 : 0000000000000481 x3 : 0000000000000000
x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffff08727d3f91e8
Call trace:
drop_nlink+0x50/0x68 (P)
vfs_unlink+0xb0/0x2e8
do_unlinkat+0x204/0x288
__arm64_sys_unlinkat+0x3c/0x80
invoke_syscall.constprop.0+0x54/0xe8
do_el0_svc+0xa4/0xc8
el0_svc+0x18/0x58
el0t_64_sync_handler+0x104/0x130
el0t_64_sync+0x154/0x158

In ceph_unlink(), a call to ceph_mdsc_submit_request() submits the
CEPH_MDS_OP_UNLINK to the MDS, but does not wait for completion.

Meanwhile, between this call and the following drop_nlink() call, a
worker thread may process a CEPH_CAP_OP_IMPORT, CEPH_CAP_OP_GRANT or
just a CEPH_MSG_CLIENT_REPLY (the latter of which could be our own
completion). These will lead to a set_nlink() call, updating the
`i_nlink` counter to the value received from the MDS. If that new
`i_nlink` value happens to be zero, it is illegal to decrement it
further. But that is exactly what ceph_unlink() will do then.

The WARNING can be reproduced this way:

1. Force async unlink; only the async code path is affected. Having
no real clue about Ceph internals, I was unable to find out why the
MDS wouldn't give me the "Fxr" capabilities, so I patched
get_caps_for_async_unlink() to always succeed.

(Note that the WARNING dump above was found on an unpatched kernel,
without this kludge - this is not a theoretical bug.)

2. Add a sleep call after ceph_mdsc_submit_request() so the unlink
completion gets handled by a worker thread before drop_nlink() is
called. This guarantees that the `i_nlink` is already zero before
drop_nlink() runs.

The solution is to skip the counter decrement when it is already zero,
but doing so without a lock is still racy (TOCTOU). Since
ceph_fill_inode() and handle_cap_grant() both hold the
`ceph_inode_info.i_ceph_lock` spinlock while set_nlink() runs, this
seems like the proper lock to protect the `i_nlink` updates.

I found prior art in NFS and SMB (using `inode.i_lock`) and AFS (using
`afs_vnode.cb_lock`). All three have the zero check as well.

公表日 2026年5月9日0:16
登録日 2026年5月9日4:14
最終更新日 2026年5月12日23:10
関連情報、対策とツール
共通脆弱性一覧