
TL; DR
- Linuxで LUKSとかLVMを壊しちゃった場合の対処方法を書いてます
- Linuxなら、困っても大体なんとかなる
- HDDのファイルシステムが破損しても復旧できた
Windowsはクソ
前情報
私はLinux Desktopユーザーで、もう5年以上メイン環境はLinuxを使っている。。 データを捨てられない性格なので、結構な容量のHDDを持っている (半分ぐらいが今まで乗り換えてきたOSのHDDイメージだったりするのだが)。
HDDの構成はこんな感じ。チョットダケ説明しよう。
平たく言うと、HDDドライブを複数枚使ってバックアップやデータ保存用の領域を作っている。その構成が若干特殊なだけ。 特殊構成にしている目的は、「セキュリティ」と「それによる不都合を相殺するため」。
構成としては、大きく3つの部分に分けられる。図の①②③である。
まず①、一番下の "/dev/sda" とあるのがHDD本体で、データが入っている。 ただし、この領域は LUKSによって暗号化されているので、そのままではデータは読めない1 。 LUKSを通して1のみデータが読めるようになっている。 この暗号化を行うことでHDDを盗まれてもパスフレーズを知らなければ開けられない(ちなみに私は12桁以上のパスフレーズを用途ごとに分けて使っている)。 (1)のセキュリティの理由。
次に②、読み込めた複数ディスクのデータをそれぞれ別枠で扱うのは面倒なので、 LVMによってグループ化している。 これによって複数のディスクを1枚として扱うことができる。 解錠されたHDDデータに直接的にアクセスするのはLVMとなる。 (2)の、暗号化による不都合の相殺のため。
最後に③、LVMで作成された論理ボリューム (LV)を適当なディレクトリにマウントして、 やっとOSから読むことができるようになる。 これも分類としては(2)で、単にこの方が使いやすくなるから。
この方式の特徴
- ディスクの入れ替えが容易 ディスクに寿命が来て不良セクタが増えてきた場合、よほどの壊れ方をしない限り^[今回は"よほどの壊れ方をしたパターン"の話です]問題なく入れ替えが行える。↓ sdaに保存されたデータを他のディスクに退避するコマンド
pvmove /dev/mapper/MAP_PATH_TO_SDA
- 盗難などに遭っても元のディスクがLUKS下にあるので、読めない
- 1枚だけに致命的な破損があった場合にも、全体が読めなくなる ← ???
何があったか
普段は「Linux万歳 外出時はMacbook」みたいな生活が長い訳だが、ある仕事でWindows環境が必要になった。 「SSDスロット空きあるしWindows入れて、使いたいときはBIOSから起動すりゃいいか〜」みたいな軽い気持ちでWindowsのISOを焼き、インストールメディアを指して空きのSSDにインストールしようとしたら、、 https://storage.googleapis.com/zenn-user-upload/edced8b64b43-20240213.png
先頭100MB * 9回分が喰われてる (8回途中で失敗した)
Windowsインストーラーはインストールの度に100MBの予備領域が確保されるようだった。
- なぜ指定していない領域に???
- LUKSを認識できなかったから空き領域だと思ったっぽい???
- ディスク先頭にある既知のマジックナンバー↓を照会するだけやろ (サボらないで)
先頭の、4c55 4b53
みたいなのがマジックナンバー、ファイルやディスクの形式ごとに存在する (これによって、ファイルシステムとかの判定をしてる)。
対処
ファイルシステムの先頭が破壊されてしまったので、復旧しなければならない。 着地点を考えてみよう。
- 元に戻す
- 一部破損でも良いから元に戻ったように見える状態で使う
- 復元ソフトで読めるファイルだけ読む
- HDD初期化
落ち着いて考えてみれば、破壊された部分は全体の極々一部なので、最悪の方法 (HDD初期化)にはならないと考えていた。実際、3つめの案で何とかなった。
元に戻そうとした過程
手順を整理しよう。
- LUKSを解除する
- LVMを再構築
- ファイルシステムチェック&マウント
この流れを辿ることができれば復元できたと言える。
1. LUKS解錠
まず、LUKSを解錠しないことには生データすら読めないので、LUKSを解錠したい。 逆にLUKSを解錠できれば、断片化されているとしても、生データにはアクセス可能なので、何とかなりそうだ。
LUKS にはキー (パスフレーズ)との照会を行うためのヘッダー部分でパスフレーズが管理されている。
パスフレーズの照会は複数枚のHDDに対して何度も入力する必要があり、毎回入力するのは手間なので、スクリプトにしている。
:::details 解錠スクリプト
#!/bin/bash
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"&>/dev/null &&pwd)" # SCRIPT_DIR
set -u
if [ ! -d "$DIR/ext/lost+found" ]; then
uuids=()
while read f; do
uuid=$(echo "$f" | awk '{print $1}')
if ! [[ $uuid =~ ^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$ ]]; then
continue
fi
uuids+=($uuid)
done< <(sudo lsblk -o UUID,NAME,TYPE | grep disk | grep -v nvme)
read -sp "Enter passphrase: " passwd; echo
cnt=0
for uuid in "${uuids[@]}"; do
id=$(echo $uuid | awk -F'-' '{print $1}')
printf "[uuid] $uuid .."
if [ "$(lsblk | grep "lv-$id")" ]; then
echo Already mounted
continue
fi
echo $passwd | sudo cryptsetup luksOpen /dev/disk/by-uuid/$uuid lv-$id
if [ $? -eq 0 ]; then
echo OK
else
echo NG
fi
done
sleep 1
sudo mount /dev/mapper/vg--ext-default $DIR/ext
fi
:::
今回確認したところ、 同じパスフレーズを使っているディスクのヘッダーは同じ内容となっていたため (以下の↓バイトコードが完全一致)、 これを /dev/sda
に転写すれば良いということになる。
cryptsetupに専用のユーティリティがあったので、それを使ってみた。
# 正常なディスクからヘッダのバックアップを取って
sudo cryptsetup luksHeaderBackup /dev/sdb --header-backup-file sdb.backup
# 壊れたほうに復元する
sudo cryptsetup luksHeaderRestore /dev/sda --header-backup-file sdb.backup
これでLUKSを解錠することができた!! (最初の図における①が解決できた)
2. LVMを再構築
LVMはそもそも少しややこしい。 PV, LV, VG の概念がある。
- 物理ディスク: HDDのこと。
- PV: LVMが認識している物理ボリュームのこと。 物理ディスク != PV であることに注意
- LV: 論理ボリューム。LVMが認識する仮想ディスクと考えて良い。
- VG: 論理ボリュームグループ。LVの集合体。
通常は ディスク → PV → LV → VG の順で構成し、VGをディレクトリにマウントして使用する。
今回は PV一覧の出力コマンドの履歴が残っていた。↓
$ sudo pvs
WARNING: Couldn't find device with uuid tLMBcr-RgOH-cdYP-4DpE-K47x-JYOT-c3sTIy.
WARNING: VG vg-ext is missing PV tLMBcr-RgOH-cdYP-4DpE-K47x-JYOT-c3sTIy (last written to /dev/mapper/lv-20e9515a).
PV VG Fmt Attr PSize PFree
/dev/mapper/lv-03e87be2 vg-ext lvm2 a-- <7.28t 0
/dev/mapper/lv-348aa360 vg-ext lvm2 a-- <7.28t 0
(省略)
[unknown] vg-ext lvm2 a-m <7.28t 0
ご覧のように、ディスクの認識が欠落していて警告が出ていて、これは通常出ない。 今回、[unknown]
と表示されているPVのUUIDは明らかに 欠落したtLMBcr-RgOH-cdYP-4DpE-K47x-JYOT-c3sTIy
なので、[unknown]
と表示されているPVを無理やり tLMBcr-RgOH-cdYP-4DpE-K47x-JYOT-c3sTIy
と認識させたい。
ここで、PVのこういったメタ情報がどこで管理されているかといえば、それはVG単位で管理されていて、VGを構成するLVやPVの情報がVG単位で記録されているので、それらを書き換えてやれば良いということになる。
具体的には現在のVGの設定をバックアップして、バックアップを編集して再適用してみる。
# バックアップ vg-extが今回の対象VGの名称です
sudo vgcfgbackup -f vg-ext.backup vg-ext
# バックアップファイルを編集
sudo vim vg-ext.backup
# PV自体のメタ情報も再生成
sudo pvcreate --uuid "tLMBcr-RgOH-cdYP-4DpE-K47x-JYOT-c3sTIy" --restorefile vg-ext.backup /dev/mapper/lv-20e9515a
# バックアップから復元
sudo vgcfgrestore -f vg-ext.backup vg-ext
# file: vg-ext.backup
pv2 {
id = "V09qt9-zuB3-zIfO-shx1-SjDA-Hjyt-nOC1U4"
device = "/dev/mapper/lv-348aa360" # Hint only
status = ["ALLOCATABLE"]
flags = []
dev_size = 15628020400 # 7.27736 Terabytes
pe_start = 2048
pe_count = 1907717 # 7.27736 Terabytes
}
pv3 {
id = "tLMBcr-RgOH-cdYP-4DpE-K47x-JYOT-c3sTIy"
- device = "[unknown]" # Hint only
+ device = "/dev/mapper/lv-20e9515a" # Hint only
status = ["ALLOCATABLE"]
- flags = ["MISSING"]
+ flags = []
dev_size = 15628020400 # 7.27736 Terabytes
pe_start = 2048
pe_count = 1907717 # 7.27736 Terabytes
}
最後にVGに変更を再読込する。
sudo vgchange -ay vg-ext
以下コマンドで警告がでなくなったらOK。
sudo pvs
sudo lvs
sudo vgs
なんとLVMを再構築することができた! (最初の図における②が解決できた)
3. ファイルシステムチェック&マウント
ここまで来たらもう少し。 ファイルシステムのチェックを。
$ sudo fsck /dev/mapper/vg--ext-default
fsck from util-linux 2.39.3
e2fsck 1.47.0 (5-Feb-2023)
ext2fs_check_desc: Corrupt group descriptor: bad block for block bitmap
fsck.ext4: Group descriptors look bad... trying backup blocks...
/dev/mapper/vg--ext-default: recovering journal
fsck.ext4: unable to set superblock flags on /dev/mapper/vg--ext-default
/dev/mapper/vg--ext-default: ***** FILE SYSTEM WAS MODIFIED *****
/dev/mapper/vg--ext-default: ********** WARNING: Filesystem still has errors **********
ダメみたい、、 強制的にマウントできないかな?
$ sudo mount -o ro,noload /dev/mapper/vg--ext-default mount-point
mount: /home/yuki/ext: mount(2) system call failed: Structure needs cleaning.
dmesg(1) may have more information after failed mount system call
# 以下はdmesg出力
[ 383.524943] EXT4-fs (dm-7): ext4_check_descriptors: Block bitmap for group 178880 not in group (block 12108825021912886424)!
[ 383.524947] EXT4-fs (dm-7): group descriptors corrupted!
無理そうです。 似たようなコマンドも試しましたが無理だった。
復元ソフトを使う
- 元に戻す ← 無理
- 一部破損でも良いから元に戻ったように見える状態で使う ← 無理
- 復元ソフトで読めるファイルだけ読む
- HDD初期化
ということで、testdisk
を使って復元してみます。
testdisk
では対象のディスク(仮想ディスク含め)を指定し、ファイル・ディレクトリをListすることができます。
復元対象を :
で指定し、C
でコピーできる。
HDDより生きているディスクが十分な容量があれば一発でいけますが、 そうでない場合は、ある程度の塊ごとに復元していく必要があります。。
面倒なのは仕方ないかと思いつつ、現在も復元中、、
脚注
改めてこのページを読んでみると、私の実装とは別のことをやっているようだ。私のものは当たり前すぎて書いてないのか🤔 「お前の実装おかしいよ」って人がいたら教えてほしい
より正確には、LUKSのパスフレーズを使って暗号化を解除した場合。別にデーモンがある訳じゃない
これもWindowsPCが必要だったので古いのを引っ張り出して頑張った
これに関しては私が悪い。WindowsPCでインストーラーを作成しなかったのが悪かった模様。Windowsの作法に従えということ。Linuxとは常識が異なる。