LANに接続している機器のIPv4アドレスを列挙する(ping絨毯爆撃) Windows無情編

続きというか、以前はUnix環境での話だったので、今回はWindowspingで絨毯爆撃してLANに接続している機器のIPアドレスを列挙してみたい。

eel3.hatenablog.com

まずはシンプルに、バッチファイルで逐次実行する方法。

@echo off
setlocal

set ADDRBASE=192.0.2.

for /L %%I in (1, 1, 254) do (
    ping -n 1 -w 100 %ADDRBASE%%%I >nul
    if not errorlevel 1 (
        echo %ADDRBASE%%%I
    )
)
endlocal

Windowspingタイムアウト値をミリ秒単位で設定する。LANに接続している機器の応答性能との兼ね合いはあるが、タイムアウト値を短くすることで実行時間を短縮することができる。

実際、このバッチファイル(タイムアウト100ミリ秒)は手元の環境では2分10秒弱で完了した。

とはいえ逐次実行である以上、どうしても時間がかかってしまう。何とかできないものか
? コマンドプロンプトでは無理そうだが、PowerShellではどうだろうか?

PowerShellで並列処理する方法はいくつかあるが、素のPowerShell 5.1の場合は、バックグラウンドジョブを使う方法とWorkflowを使う方法の2つが考えられる。

バックグラウンドジョブを使う方法で、何も考えずにシンプルにコードを書くと、こんな感じになる。

Set-StrictMode -Version Latest

1..254 | %{
    $addr = "192.0.2.$_"
    Start-Job -ScriptBlock {
        param($addr)
        $out = ping -n 1 -w 500 $addr
        if ("$out" -cnotmatch '100%') {
            $addr
        }
    }
} | Wait-Job | Receive-Job

このスクリプト、手元の環境では5分待っても処理が終わらないのである。タスクマネージャーで確認したところ、大量のプロセスが生成されて動作していた。プロセスの生成数を制御した方がよさそうだ。

ということで、アレコレと試行錯誤してたどり着いた、手元の環境で最も高速なスクリプトはこんな感じ。

Set-StrictMode -Version Latest

Set-Variable -Name ADDRBASE -Value '192.0.2.' -Option Constant

$jobs = @()
for ($i = 1; $i -le 254; $i++) {
    $jobs += Start-Job -ArgumentList "$ADDRBASE$i" -ScriptBlock {
        param($addr)
        $out = ping -n 1 -w 500 $addr
        if ("$out" -cnotmatch '100%') {
            $addr
        }
    }
    $tmp = $jobs | ?{ $_.State -eq 'Running' }
    if (($tmp | Measure-Object).Count -ge 5) {
        Wait-Job $tmp -Any | Out-Null
    }
}
Wait-Job $jobs | Receive-Job | Write-Host
Remove-Job $jobs

信じられないかもしれないが、for文を使っているのもループ中でWait-Jobを実行するタイミングもループ中のWait-Jobのオプション-Anyも、全て手元の環境での高速化に結びついている。現物合わせでこうなった。

このスクリプトは……プロセスを常時5個以上立ち上げてCPUをガンガン占有して処理を行う割に、手元の環境では2分16秒ほどかかる。バッチファイルによる逐次実行版の方が数秒速い上に、CPUをほとんど占有しない。どういうことだ!

バックグラウンドジョブに失望したので、PowerShell 3.0で導入されたWorkflowを使ってみた。

Set-StrictMode -Version Latest

workflow Ping-All {
    ForEach -parallel ($i in @(1..254)) {
        $addr = "192.168.24.$i"
        $out = ping -n 1 -w 500 $addr
        if ("$out" -cnotmatch '100%') {
            $addr
        }
    }
}

Ping-All | Sort-Object { [Version] $_ }

リソースの占有具合はマシになったものの、やはり手元の環境ではそれなりに時間がかかる。とはいえバッチファイルによる逐次実行版よりは少し高速になった。

Windows付属のPowerShellでの並列処理の遅さには不満がある。標準でThreadJob入りになるとよいのだが……今後、PowerShell Coreベースの実装が標準添付されるようになるのかどうかも含めて、少し気になるところである。

LANに接続している機器のIPv4アドレスを列挙する(ping絨毯爆撃)

たまにLANに接続している機器のIPアドレスを知りたい時がある。PCやゲーム機あたりなら本体の機能でIPアドレスを確認すれば済む。しかし相手がホームゲートウェイブロードバンドルータ無線LANアクセスポイントやUSBメディアサーバで、ハードウェア本体にはIPアドレス表示機能がなく、Web管理画面からログインしたいのだけどアドレスが分からないので管理画面にすらたどり着けない──そんな時に、とりあえずLANに接続している機器のIPアドレスを列挙して、そこから絞り込んでいったりする。

UPnPDLNABonjour経由でWeb管理画面にたどり着けるとは限らない。古い機器なのでUPnP的な機能を持っていなかったり、クライアントPCがWindowsでいうところの「ネットワーク探索」を無効にしていたりすることもあるのだ)

他には、例えばLANにネットワーク対応のプリンタを接続して固定IPを割り振りたいのだけど既存のサーバやプリンタその他が使用しているIPアドレスが分からないとか、Raspberry Pi OSを入れたRaspberry Piの初期設定をしたいのだけど諸事情によりディスプレイもキーボードも接続できないので初っ端からssh接続したいとか、そういう場合にも同じようなことをしたくなる。

安直な解法としては、LANの全IPアドレスに対してpingを実行して反応を調べればよい。ファイアーウォール等でpingに応答しない設定にしている機器は列挙できないが、大抵の場合、応答しないのはPCだ(PCだから普通にログオンしてIPアドレスを確認すればよい)。

という訳でping(8)で絨毯爆撃するシェルスクリプトを書いてみた(IPアドレスは各自の環境に合わせて調整すること)。

#!/bin/sh
# -*- coding: utf-8-unix -*-
# vim:fileencoding=utf-8:ff=unix
#
# Worked on Ubuntu 20.04

readonly addrbase=192.0.2.

for i in $(seq 1 254); do
    addr=$addrbase$i
    ping -c 1 -w 1 $addr >/dev/null
    [ $? -eq 0 ] && echo $addr
done

環境によってping(8)のタイムアウトを設定するオプションが異なるので注意。

このスクリプトは、安直に「タイムアウト1秒の設定で1回だけICMP Echoを送信する」という処理をIPアドレスごとに順番に実行している。そのため非常に遅い。LANに接続している機器が少なかったり、接続していてもpingに反応しない機器が多いほど――つまりICMP Echo Replyが返ってくる可能性が低いほど遅くなる。最大で1秒かかる処理を254回実行するので、ワーストで4分13秒ぐらいかかる計算になる*1

4分以上もかかるのは不便なので、高速化してみた。

#!/bin/sh
# -*- coding: utf-8-unix -*-
# vim:fileencoding=utf-8:ff=unix
#
# Worked on Ubuntu 20.04

readonly addrbase=192.0.2.

# do_ping <IP address>
do_ping () {
    ping -c 1 -w 1 $1 >/dev/null
    [ $? -eq 0 ] && echo $1
}

{
    for i in $(seq 1 254); do
        do_ping $addrbase$i &
    done
    wait
} | sort -V

バックグラウンドプロセスを使ってping(8)を並行実行させるようにしてみた。これなら1秒ちょっとで完了する。

並行実行の代償として、IPアドレスの出力順が不定となる。これをソートするのに、IPv4アドレスならGNU coreutilsのsort(1)のオプション-Vが使える。他の環境には-Vが無いので、別のオプションの組み合わせで対応することになる(例えばsort -t . -k 4,4 -nだろうか)。

なおIPv6のことは考えていない。自宅や会社のLANがIPv6になってから考えるつもり。

終わりに

最終的には、こんな感じになった。

https://github.com/eel3/eel3-scripts/blob/master/bin/linux/pingest4

*1:4分14秒ではなく4分13秒なのは、LANに接続している機器のうち1台は自分自身なので、ほぼ確実に数ミリ秒程度でレスポンスが帰ってくるからだ。

「プログラミング経験者向けのCプログラミングの入門書」が欲しい

タイトルに書いたことが全てだが、思考の整理を兼ねて、もう少し深掘りしてみたい。

以下に示す要求に合致した書籍の類を探しているのである:

  1. C言語(言語仕様)よりも「C言語を用いたプログラミング」に主軸を置いた内容。
  2. 最低でもC99、できればC11/C17の利用を前提とした内容とコーディングスタイル。
  3. プログラミング未経験者ではなく「すでに他のモダンな言語によるプログラミングを経験している人」を想定した内容。
  4. C言語(や古典的なC++)でのプログラミングに付き物の「変数と記憶領域」についての解説がある。

イメージとしては「『プログラミング言語C 第2版 ANSI規格準拠』と『Cプログラミング専門課程』を足して2で割って近代化改修して若干マイルドにしたもの」だろうか?

プログラミング言語C 第2版』のターゲット層は1970~1980年代基準の「コンピュータとプログラミングの基礎」を知っている人*1であるし、内容も「Unix系OSのシェルないし類似環境を想定した、ホスト環境向けのCプログラミング」に軸足が置かれている*2。想定読者はプログラミング経験者であるし、内容も「C言語の言語仕様」よりも「Cプログラミング」の入門書としての側面が大きいといえる。

残念なことに『プログラミング言語C 第2版』は進化が止まって久しい*3。Cプログラマの血と汗の結晶である「言語仕様の落とし穴を回避するための防御的なコーディングスタイル」や、モダンな言語仕様による「より楽で明快な書き方」といった側面が抜け落ちたままとなっている。それ故に、Cプログラミングの中級~上級者向けの奥義書としての価値はまだ残っているものの、Cプログラミング初級者に読ませるには問題がありすぎる、という扱いの難しい本になってしまっている。

『Cプログラミング専門課程』は、Cプログラミングとは切っても切れない「変数と記憶領域」について大々的に解説している本だ。細かい部分は置いておくとして、本書で学んだC言語におけるメモリの扱いについての基本的な考え方は、組込み向けのCプログラミングにも流用できるだろう*4

残念ながら本書は絶版しているらしく、入手が難しい。

あと「ANSI CではなくK&R Cでサンプルが書かれている」という特徴もある。C言語分かっている勢なら問題にならないのだけど、C言語の初学者に読んでもらうには問題がある。この点より、「本書を薦めても問題ない読者」のレベルが「本来、この本を読んで欲しい読者」のレベルよりも若干高めになってしまうという、読者層のミスマッチが起きているように思う。

「ぼくのかんがえたさいきょうのCプログラミング入門書」の内容は、『プログラミング言語C 第2版』と『Cプログラミング専門課程』の抱き合わせだ。どちらも、まずサンプルコードのスタイルを近代化(ANSI C対応+防御的なコーディングスタイルに書き換え)するだけでも、喜ぶ人がいると思うのだ。その上で、C11/C17に対応していたら、もう言うことはない。

対象読者については、最近の人がC言語でプログラミング入門するとは思えないので、そろそろ「プログラミング未経験者向け」という制約を取り除くべきだろう。今日日のC言語入門者って、PythonJavaScript/TypeScriptなどの「モダンなプログラミング言語」でプログラミングしたことがある人が大半だと思う。だから、手続き型プログラミング言語の諸々の基本概念についてすでに知っていると仮定した上で、「C言語でプログラミングする場合にはこう書く/こうアプローチする」みたいなスタイルで説明しても許されるように思うのだ。

一方で、『プログラミング言語C 第2版』の頃には考えにくかったシチュエーションだと思うのだが、Pythonなどの「モダンで安全なプログラミング言語」の経験者からすると、C言語でのプログラミングは「原始的で野蛮なプログラミング言語による縛りプレイ」である。その点について、教育用のコンテンツを作成するにあたり、従来とは違う角度からのアプローチが求められるのかもしれない。

開発環境については、素人のことを考えなくてもよいならば、2023年の時点では「Unix系のシェル環境」に一本化しても大丈夫かもしれない。WindowsにもWSLがあるし、何なら学習環境を一括導入できるDockerイメージを用意することもできる。どちらもWeb系のソフトウェア開発経験者なら触ったことがあるだろう。

つらつらと書き連ねてきたが、タイトルにもあるように、モダンな「プログラミング経験者向けのCプログラミングの入門書」が欲しいのである。何か良い本はないだろうか?

*1:コンピュータ科学の基礎知識を持っていて、かつ他の言語(時代的にBASIC・FORTRANPascalあたりだろうか)でのプログラミング経験がある人。

*2:だから、言語仕様の細かい話については、実のところ他の書籍を参照した方がよい。

*3:少なくとも日本語訳については。原書については知らない。

*4:n=1の感想なので、断言まではできないが、私に場合は流用できた。

書籍購入:『C言語本格入門 基礎知識からコンピュータの本質まで』

故あってC99以降に対応したC言語の入門書を探している(4回目)。自習用として問題なさそうなやつ。あと、プログラミング初心者向けの冗長な内容ではなく、プログラミング経験者でも読み進めやすい簡潔な内容のもの。

ぱっと見だが、難易度の面で、少しだけ評価が難しい本だと感じる。

うーん、「C言語でプロダクション向けのコードを書くことになった人」向けの副読本、だろうか?

プログラミング未経験者は、もっと平易なC言語の入門書で学習して、何かしらの「実用的なCLIツール」の劣化コピーを2~3個ぐらい書いてから、本書を手に取った方がよいだろう(それでも当初は手に余るかもしれない)。

プログラミング経験者でも、モダンな高水準言語での経験しかないようならば、他のC言語の入門書と併用した方が安全だろう。

C言語C++*1で何か実用的なものを書いたことがある人なら分かると思うのだが、一般的な高水準言語とは異なる点として、C言語によるプログラミングでは「言語という抽象化層」に穴を空けて下の層を覗き込むことが多々あるし、また下の層の事情を知ることでCプログラミングのレベルが上昇することもある。特に変数と記憶領域(メモリ)がそうだ。

「言語という抽象化層」に穴を空けられる点がC言語の特徴であり、メリットなのだが(でなければOSやデバイスドライバファームウェアの記述言語になれなかっただろう)、反面それがCプログラミングの難しさというデメリットの主要因ともなっている。

本書ではそういう「C言語の面倒くさい部分」も扱っている。お仕事でC言語を使う際に直面する現実でもあるので、C言語でプロダクション向けのコードを書くならば、本書を手に取ったほうがよいだろう。

ただし難易度に注意すること。本書の難しさって、C言語の難しさではなくて、プログラミング言語という抽象化層の下に隠されている「コンピュータ」そのものの面倒くささに起因する部分が多いので、プログラミングのベテランでも下位層の知識が不足していればつまづく可能性があるように思う。

追記:それにしても『Cプログラミング専門課程』復刊してくれないだろうか?

*1:特に古典的なスタイルのC++のこと(モダンC++ではない)。

書籍購入:『独習C 新版』

故あってC99以降に対応したC言語の入門書を探している(3回目)。自習用として問題なさそうなやつ。あと、プログラミング初心者向けの冗長な内容ではなく、プログラミング経験者でも読み進めやすい簡潔な内容のもの。

初めて『独習C』を手に取ったので旧版の実情は知らないのだが、この新版については、プログラミング経験者向けのC言語の入門書という観点では、悪くない選択肢だと思う。

1つの言語でも構わないので、モダンな汎用のプログラミング言語でそこそこ本格的なコードを書いた経験があるならば、C言語を学ぶ際に本書を手に取っても問題ないはずだ。

言い換えると、プログラミング未経験者や、経験者でも「雰囲気でコピペしてた」系の人だと、本書はやや手に余るだろう。

追記:本書の出版時期の都合より仕方ない部分ではあるが、(2023年の視点では)学習環境はUnix環境に統一してしまい、コンパイル実行例でcc(1)を使うようにしても問題ないかもしれない。今ではWindowsでWSLが使えるし、Linuxgcc(1)macOSclang(1)cc(1)で呼び出せる(エイリアスになっている)ことが多いからだ。

書籍購入:『ザ・理工系のためのC C99準拠』

故あってC99以降に対応したC言語の入門書を探している(2回目)。自習用として問題なさそうなやつ。あと、プログラミング初心者向けの冗長な内容ではなく、プログラミング経験者でも読み進めやすい簡潔な内容のもの。

……うーん、アカン、サンプルコードのmain関数の定義が「main()」になっている。C99規格のモードでコンパイルできない気がする*1

というか「main()」と「int main(void)」が混在している。複素数のサンプルコードでは「int main(void)」が使われているから――恐らく大半のサンプルコードはC90向けのものをどこかから流用していて(だから「main()」になっている)、新規作成したサンプルのみ「int main(void)」になっている、といった感じではないだろうか。

自習用としては難あり、かなあ? チューターがいるなら別かもしれない。

*1:多分、ClangやGCCで-std=c99付きでコンパイルすると、戻り値の型が書かれてなくてコンパイルエラーになると思う。

書籍購入:『C言語プログラミング入門 C99対応』

故あってC99以降に対応したC言語の入門書を探している。自習用として問題なさそうなやつ。あと、プログラミング初心者向けの冗長な内容ではなく、プログラミング経験者でも読み進めやすい簡潔な内容のもの。

……うーん、良さそうに感じたけど、やっぱりアカンかあ。ちょいちょい微妙な部分があるぞ。

ちょっと見た範囲で、不味そうな部分:

  • 「1.7 プログラム中のコメント」に以下の記載があるが、そんなはずはない。

ただし,厳密なC99の規格では "//" でコメントを書くとエラーになるようである。

  • 「8.1.2 外部変数 (extern)」に以下のような記載があり、ぱっと見で「外部変数(グローバル変数)を使う際に、各関数内でextern宣言すること」を推奨しているように読めるが、そのようなスタイルは一般的ではない。どちらかと言えば、規模が大きなプログラムや、複数人数での開発においては、極力「外部変数を直接参照する手法」を排したスタイルで設計・実装するべきだろう。

外部変数は,上の例のようにプログラムの先頭で宣言するだけでなく,各関数の中でもextern文を用いた使用宣言をするのが正式の使用方法であるが,実際には外部変数をプログラムの先頭で宣言するだけで,extern文による使用宣言は省略することが一般的である。しかし,プログラムサイズが大きくなったときや,何人かで協力してプログラムを作る場合などは,各関数の中でextern文による使用宣言をしておくほうが失敗が少ないと思われる。

  • 「8.1.3 静的変数 (static)」で扱っているのが「関数内のstatic変数」だけで、staticを付与した外部変数(≒ファイルスコープ変数)についての記載がない。ついでに言えば、staticを付与した関数(ファイルスコープの関数)についての記載もない。

自習用としては難あり、かなあ? チューターがいるなら別かもしれない

NAND型フラッシュメモリ直付け時代の内蔵ディスクの残存データ対策

身近な汎用のコンピュータの内蔵ディスクがHDDからNAND型フラッシュメモリSSDやeMMC)に移行して久しいが、あらためて内蔵ディスクの残存データの取り扱いについて考えたい。

バイスの特性に基づく考え方の変化については、HDDからSDDへの移行期に語りつくされたように思うし、当時の議論については2023年現在も引き続き有効だと考えている。

それとは別に1点:特にスマホ等で顕著だが、最近はNANDメモリやeMMCがコンピュータ本体のロジックボードに直付けされていることも多い。あと筐体の分解難易度が確実に上昇している。

つまり、内蔵ディスクを取り出すことが容易ではなくなった。この点も残存データの取り扱いに影響を及ぼすように思う。

おさらい (1):そもそも「消したはずのデータ」が残っている可能性は?

OS上でファイルを削除した時、HDDの場合、ファイル管理領域の情報だけ削除されて、データ領域のセクターにはデータが残存している――という点がHDD時代のデータ消去事情の根底にあった。

SSDが使われるようになって生じた変化は、Trim/UNMAPの登場だろう。OSがファイルを削除した時、使わなくなった領域の情報をSSDに通知することで、SSDが当該領域に該当するブロックのデータを消去する――という仕組みである。

最近のOSにはTrim/UNMAPを発行する機能があるし、SSD側もTrim/UNMAPに対応している。つまり、削除したファイルのデータが、Trim/UNMAPによってNANDメモリからも消えている可能性がある。

問題は、NANDブロックのデータ消去はそれなりに時間がかかる(その分だけファイルI/Oの性能に影響が出る)処理であるため、OSやNANDフラッシュメモリコントローラが色々と賢いことをやっている、という点にある。

OSは、ファイル削除の度にTrim/UNMAPを発行する訳ではない。バッテリー駆動などの省電力モードではTrim/UNMAPを全く発行しないこともある。コントローラも、性能低下を避けるために、Trim/UNMAPを受けたタイミングとデータを消去するタイミングが異なる可能性がある(コントローラのファームウェアアルゴリズムに依存する)。

HDD時代ほどあからさまではないものの、SSDやeMMCにおいても、OS上で削除したファイルの中身のデータがNANDメモリのブロックに残っている可能性がある、と考えてよいだろう。厳密にはOSやコントローラの振る舞いに依存する話になりそうだが、コントローラなんて(ファームウェアのバージョン違いまで考慮すれば)山ほど種類があるのだから、個別対応じゃなくて最悪のケース――データが山ほど残っている可能性を想定した方が安全だろう。

USB接続のデバイスの場合、もう少し複雑になる:

  1. UAS (USB Attached SCSI) 未対応なら、Trim/UNMAPにも未対応である。
  2. UAS (USB Attached SCSI) に対応していれば、Trim/UNMAPに対応できるはずだが:
    1. バイスの仕様として、Trim/UNMAPに対応していない可能性がある。
    2. バイス内部で使用しているUSB-SATA/NVMeブリッジが、Trim/UNMAPを中継したふりをしている可能性がある。
    3. これらの関門をクリアできれば、あとはコントローラの問題となる。

2023年9月の時点では、USBメモリやUSB-SSDについては、Trim/UNMAPの効果には期待しない方がよいだろう。HDDと同様に「残存データがある」という前提で考えるのが無難だ。

おさらい (2):NAND型フラッシュメモリの特性と残存データ対策

NAND型フラッシュメモリの残存データを何とかしようとしたら、次の2択しかないだろう。

  1. SSDメーカーが提供しているソフトを使ってSecure Eraseする。
  2. フラッシュメモリのチップやeMMCモジュールを物理破壊する。

NAND型フラッシュメモリはコントローラとセットで用いられる。この点が事を複雑にしている。

HDD時代から用いられてきたデータ消去のアルゴリズムは、SSDやeMMCでは有効ではない。HDD向けのデータ消去では、データが残存しているセクターにたいして無意味なデータを上書きしている。フラッシュメモリでは、データ書き込みはコントローラ経由で行われるが、コントローラはウェアレベリングやBad Block管理も踏まえた動作をする。そのため「残存データのあるブロック」にデータが上書きされることを保証できない。

フラッシュメモリのブロックの残存データを消すには、俗に言うSecure Eraseを実行することになる。ATA CommandやNVMe I/O Commandにはデータ消去に使えるコマンドがある。データ消去のコマンドをデバイスにたいして発行すると、デバイスが対応していればデータが消去される。

コマンドを受けて処理を実行するのはコントローラだろうから、コントローラがデータ消去のコマンドに対応していて、コントローラがちゃんと動作してくれれば、データが消去されるはずである。

言い換えれば、次のケースでは、データが消去されない可能性がある。

  1. コントローラがデータ消去のコマンドに対応していない、または対応しているふりをしているだけ。
  2. コントローラのデータ消去関連の機能に不具合がある。
  3. コントローラが壊れかけていて、データ消去関連の機能に支障が出ている。
  4. コントローラが故障していて、動作しない。

一般的に、NANDメモリを自社製造しているような大手SSDメーカーの製品は、メーカー公式のデータ消去ツールも存在することから、コントローラを含めてデータ消去のコマンドに対応していると考えてよいだろう。だから、デバイスが正常ならば、データ消去を期待しても大丈夫なはずだ。でもデバイスが妙な振る舞いをするようならば、コントローラが壊れかけているかもしれないので、ツールによるデータ消去は期待せず、NANDメモリチップを物理破壊した方が安全だろう。

大手メーカー以外の、有象無象の格安製品では、最初から物理破壊するべきだろう。そもそもNANDメモリだけでなくコントローラも偽造品かもしれないので、Secure Eraseに期待しない方がよい。

さて、ATA CommandやNVMe I/O Command云々は、内蔵ディスクとして用いるSSDやeMMCの話である。

USB接続のデバイスの場合、もう少し複雑になる:

  1. UAS (USB Attached SCSI) 未対応なら、Secure Eraseする術がない。
  2. UAS (USB Attached SCSI) に対応していれば、SCSI Commandのデータ消去のコマンドが使えるはずだが:
    1. バイスの仕様として、データ消去のコマンドに対応していない可能性がある。
    2. バイス内部で使用しているUSB-SATA/NVMeブリッジが、データ消去のコマンドを中継したふりをしている可能性がある。
    3. これらの関門をクリアした上で、先に述べたようなコントローラの問題も大丈夫ならば、データが消去されるはず。

少なくとも、2023年9月の時点では、USBメモリやUSB-SSDのデータ消去は、あまり期待すべきではないだろう。まあ、一般的に外付けデバイスは物理破壊しやすいことが多いのだから、最初から物理破壊を試みてもよい気がする。

おさらい (3):NAND型フラッシュメモリの残存データの復元難易度

仮にSecure Eraseを期待できないデバイスが故障したとして、残存データを吸い出されて復元される可能性は、どの程度あるだろうか?

まず、NANDメモリ自体が故障・破損している場合、残存データの吸い出し自体が難しいだろうし、仮に吸い出しに成功したとしてもデータ自体が損傷していると思われる。

次に、NANDメモリは生きているがコントローラが故障している場合、全く同じコントローラ(型番だけでなくファームウェアバージョンも同じもの)に差し替えることで、データを吸い出せる可能性がある。ただし、そもそも「型番もファームウェアバージョンも同じコントローラ」を入手すること自体が難しい、ということは考えておくべきだろう。

コントローラを経由せずにNANDメモリからデータを吸い出した場合、吸い出したデータから「意味のあるデータ」を復元するのは難しい。そもそもコントローラがウェアレベリングやらBad Block管理やらと色々と配慮したアルゴリズムによって分散したブロックにデータを書き込むし、アルゴリズム自体がコントローラの型番やファームウェアバージョンによって異なる。データ復旧業者が「SSDのデータ復旧は難しい」と言う理由の一端である。

ただ、「意味のあるデータ」に復元するのは難しくとも、データ自体は吸い出される可能性があるし、曲がりなりにもデータ復旧業者が成り立つ程度には「データが復元される」可能性もある。

結局のところ、残存データの吸い出しと復元を気にするならば、NANDメモリ自体を破壊して吸い出し不可能にしてしまおう――ということになる。

バイスが取り外し可能なら物理破壊が確実だが……

SSDやeMMCのデータ消去においても、HDDと同様に、物理破壊は非常に有効である。

であるのだが、HDDとは異なり、SSDやeMMCはコンピュータ本体の基板に直付けされていることがある。この点が「物理破壊最強説」に影を落としているように思う。

色々と考えてみた結果を表にするとこんな感じ:

バイスの取り外し 本体/デバイスの生死 残存データ対策
取り外し可能 動作する 物理破壊 or Secure Erase
取り外し可能 動作しない 物理破壊
取り外し不可能 動作する Secure Erase or ???
取り外し不可能 動作しない ???

バイスが取り外し可能ならば、物理破壊が最強である。大手メーカーのSSDならばSecure Eraseも悪くないが、有象無象のSSDではコントローラの信頼性(Secure Eraseに適切に対応しているか否か)が怪しい。何よりも、デバイスが故障していたらSecure Eraseできない。

ではデバイスが本体の基板に直付けされていて取り外し不可能な場合はどうだろうか?

そのデバイスを搭載したコンピュータ自体が生きていて、なおかつSecure Eraseを明示的に実行できるならば、データを消去できるだろう。

では、デバイスを搭載したコンピュータが故障してしまった場合はどうだろうか? あるいは、コンピュータは生きているものの、何らかの理由で「Secure Eraseが実行されること」を保証できないシステムの場合はどうだろうか?

バイスを取り外し可能ならば、取り外して物理破壊することも、他のコンピュータに移植してSecure Eraseを試みることもできる。でもデバイスが基板に直付けだと移植は難しいし、筐体の分解も難しいならば物理破壊の難易度も高くなる。

最悪なのは、次のような事態である:

  1. 故障により基板直付けのSSD/eMMCにデータが残ったままのコンピュータを破棄した。
  2. 故障した部分は、NANDメモリやコントローラ以外だった。
  3. このコンピュータをジャンク品として入手した人が修理できてしまった。
  4. NANDメモリやコントローラは生きていたので、データを読み出せてしまった。

さてはて、いったいどうしたものか?

「どう頑張っても残存データは生じる」という前提に立つ――ディスク暗号化

Secure Eraseも物理破壊も難しい状況が考えられるのならば、残存データそのものをどうにかしようと頑張ることは不毛だろう。残存データが生じることを前提に考えるべきだ。

現時点で有効なアイデアは「ディスク暗号化」と「耐タンパ性を備えたセキュリティチップ」と「『複雑なパスワード』ないし生体認証」を組み合わせることだろう。

ディスク暗号化は、WindowsではBitLockerを、macOSならFileVaultを導入すればよい*1。ディスクを丸ごと暗号化しておけば、NANDメモリから吸い上げたデータより「意味のあるデータ」を復元したとしても、復元されたデータは暗号化されている。復号されない限り大丈夫だ。

復号用の秘密鍵が漏れないように、耐タンパ性を備えたセキュリティチップを備えたハードウェア使用すべきだろう。BitLockerはTPMと組み合わせるべきだ。macOSについては不明だが、T2セキュリティチップや、M1以降のSoCに統合されたセキュリティ機能は、おそらく耐タンパ性を備えているはずだ。

「ディスク暗号化」と「耐タンパ性を備えたセキュリティチップ」の組み合わせは、コントローラを経由せずにNANDチップから直接データを吸い出されるケースへの対策として十分に機能するはずだ。

その上で、NANDメモリやコントローラ以外の部分が故障していたコンピュータを修理されてしまった場合への対策として、簡単にシステムにログインできないように認証機能を有効化しておくべきだろう。

パスワードを使うならば、複雑なパスワードを設定しておくべきだ。生体認証(指紋認証や顔認証など)が使える機器ならば、パスワードではなく生体認証を選択してもよいだろう。Windowsでは、BitLockerのOS起動前認証を併用するのも良いアイデアだ。

機器を廃棄する時は、最低ラインとして「ディスクを暗号化した状態でOSを再セットアップ」すればよい。重要ファイルの残存データはNANDメモリに残っているが、暗号化されている。復号できなければよいのだ。

Windowsでは、OSの機能を使用してシステムを初期状態に戻せばよい。その際にTPMをクリアすれば、「『初期状態に戻す前に暗号化したデータ』を復号するための秘密鍵」が消えてなくなり、復号が困難となる。

macOSでは、FileVaultを使用した状態で、サポート情報(ここここ)を参考に作業を進めればよいだろう。

取り外しが容易な内蔵ディスクでは、上記作業を行った後に、追加作業としてSecure Eraseや物理破壊を行ってもよいだろう。なお物理破壊の際にはNANDメモリのチップを狙うこと。

このアイデアの注意点は、例えばOSのシステムファイル破損などのソフトウェア要因でシステムが起動しなくなった場合に、USBブートさせたOSを使って重要ファイルを救出することが困難である可能性が高いことだ。

ディスク暗号化するならば、外部メディアへのデータバックアップと、バックアップ先メディアの安全な保管が欠かせないだろう。

まとめ

内蔵ディスクとしてNANDフラッシュメモリを使用する機器では、基板直付けで取り外せない可能性などを考慮すると、「残存データを何とかする」という方向性ではなく「残存データが生じる前提で対策を考える」という方向で考えておいた方がよいだろう。

現時点で有効なアイデアは、以下の3つを組み合わせることだ:

  • ディスクを丸ごと暗号化する。
  • TPMなどの「耐タンパ性を備えたセキュリティチップ」を使用する。
  • 機器にログインする際の認証に「複雑なパスワード」ないし生体認証を用いる。

機器を廃棄する時は、ディスクを暗号化したままの状態で、「セキュリティチップに保存された秘密鍵の消去」と「OSの再セットアップ」を行えばよい。WindowsmacOSも、標準のシステムリセットの方法が用意されている。

副作用として、故障時のデータ救出難易度が高くなるので、重要データのバックアップとバックアップ先メディアの安全な保管が欠かせなくなる点に注意すること。

なお、最終手段としての物理破壊は、SSDやeMMCでも非常に有効である。破壊する際にはNANDメモリのチップを狙うこと。

*1:LinuxならLUKSだろうか?

PINコード(数字のみ)を生成する

暗証番号やPINコードに使えるような、指定された桁数のランダムな整数値を生成したいのである。

英数記号を用いたパスワード文字列の自動生成については知見があるのだが、「数字のみ」という条件が付いた途端に、「あれ? そういえば、どう生成すればよいのかなあ?」とつまづいてしまったのである。

パスワード文字列の生成ではpwgen(1)を愛用している。他のツールを探すのも面倒なので、pwgen(1)で何とかできないだろうか?

man(1)を見ながら検討した結果、手元のpwgen 2.08ではこんな感じで数字のみのパスワードを生成することに成功した。

# 6桁のPINコードを1つ生成する場合:
pwgen -A -r abcdefghijklmnopqrstuvwxyz -s 6 1

オプション-Aを指定して英大文字を除外した上で、オプション-rを使って英小文字を除外している。オプション-yを指定しなければ、記号文字は使われない。消去法で、残された有効文字は数字のみとなる。

他にもっとスマートなツールが存在するはずだが、とりあえず個人的に使う分にはこんなもので十分だろう。

終わりに

最終的には、こんな感じになった。

https://github.com/eel3/eel3-scripts/blob/master/bin/genpin

『単体テストの考え方/使い方』ファースト・インプレッション

パッと見の印象:テスト駆動開発あたりの文脈から出てきた「単体テスト」についての、「良いテスト」について論じた本。

単体テストにフォーカスしたソフトウェア・テストの本で、しかも「具体的なテストのやり方」ではなく抽象的な視点で論じているものって、(少なくとも和書では)あまり類書を見ないように思う。

本書を購入した理由は、私の中の「単体テスト」と、最近に世間一般で言われている「単体テスト」の差異を知るためである。

私自身は、どちらかと言えば組み込みソフトウェア開発の文脈で単体テストに取り組んできた人である。組み込み系の単体テストは、本書の「単体テスト」とはまた異なる趣がある。

知らないうちに、自分と相手とで異なる「単体テスト」を思い浮かべたまま会話していて、話がこじれてしまった――みたいなことを避けたい。そのためにも、今の時代に多くの人が思い浮かべるだろうタイプの「単体テスト」について知っておきたい。

こういう用途には、今の「単体テスト」について一通りまとめられた本書はちょうど良い感じである。

ただし、この本は教科書や学術書ではなく、「著者が自分自身の開発経験を通じて会得した考え方をまとめたもの」という面が大きい。そのため、内容の所々に著者の好みが現れているように感じられる。全てを鵜呑みするのは避けた方がよいだろう。