Chienomi

(Coinhive事件絡み) プログラムってなんやねんって話

ニュース::criminal

Coinhive事件の高裁での逆転有罪にコンピュータ業界が揺れている。 とんでもなくありえないし、ツッコミどころ満載だからだ。 これは、全てのプログラムをしたことのある人間を好きに罰せる法律であることを示したといっていい。

だが、プログラムを作ったことのない人にとってはわからないことでもあるので、簡単に説明しよう。

Coinhiveの件を説明するのは2回目だが、前回とは主旨が違う。

「意図したように」動くか

第一に、当たり前だけれど書いた人にとってはプログラムに対する明確な意図があり、その意図のためにプログラムを書いているのだ。

だが、「意図したように動くプログラム」というのは、「理論上の存在」(=架空の存在)であり、実在しない。 それは、「バグのないプログラム」と同じである。 したことのない人からすればできそうに思えるかもしれないが、現実には不可能である。

これは、スポーツなどにおいて「記録はAのほうが優れているのだから、Aが常にベストを出し続ければ絶対負けない」と言っているのと同じことである。

意図したように動かすことは絶対不可能ではないが、プログラムが意図した通りに動くものにすることはできない。 これはかなり説明も難しい理由も絡んでの複雑なことだが、主には「コンピュータは複雑なものだから」である。

複雑なものだからその全てを把握することはできないし、また将来に渡ってその全てをコントロールすることができない。 そのために、どれほど簡単なプログラムであっても、それは「意図を反映して書く」ことが可能なことの限界であり、それがどう動くかを保証することはできない。

プログラムはそれを書いた人はなんらかの意図を持っているはずだし、書いた張本人の意図を実現するのが最も簡単だ。 だが、それすらも現実には不可能なのである。

「期待したように動く」プログラムを書くことは可能かもしれないし、もしそれが実現すれば大成功といえるだろう。 だが、「意図した通りに動く」プログラムを書くことはできない。プログラムは常に、意図したとおりではなく、書いた通りに動く。

極めて簡単なプログラムが意図した通りに動作しないなんてありえない、と思うだろうか?

例えば次のプログラムは、ファイル greet.txt というファイルに対してHelloという文字列を書き込むプログラムである。 と、私は意図するし、それは容易に察することができるほど単純なコードである。

File.open("greet.txt", "w") {|f| f.puts "Hello" }

だが、私はこれが意図した通りに動かない状況を知っている。

$ mkdir mygreet
$ chmod 500 mygreet
$ cd mygreet
$ ~/greet.rb
Traceback (most recent call last):
    2: from /home/haruka/greet.rb:1:in `<main>'
    1: from /home/haruka/greet.rb:1:in `open'
  /home/haruka/greet.rb:1:in `initialize': Permission denied @ rb_sysopen - greet.txt (Errno::EACCES)

次のコードは画面に Hello, world! と表示するプログラムである。 書籍で一番最初に出てくるようなことも多いだろう。

puts "Hello, world!"

書籍で出てくるからには、そういう意図を持ったコードであるのは明らかである。 だが、私はこれがその意図に背く状況を知っている。

$ hello.rb >&-

動いていたシステムがOSや言語処理系のアップデートによって異なる動作をしたり、動かなくなったりするのはごく日常的にあることだ。 もちろん、開発者とは異なる環境で動作させようとしたときに、意図した通りに動作しないのも当たり前に起りうる話であり、すべての環境を想定して作ることなど決してできない。

「意図する」ことが可能か

さて、あなたのコンピュータがどのように動作するかについて、あなたが意図することは可能だろうか。

意図するというのは一方的な期待であり、何らかの根拠に基づいているわけではない。 例えば、「一万円札を大量に発生させるためにスマートフォンを使ったのに、わいてこない!意図に反している!」というのが成立してスマートフォンメーカーは逮捕されるべきである、とあなたは考えるだろうか?

「ユーザーの意図に反する」とはそういうことである。

そもそも、コンピュータを意図した通りに動作させることなど決してできない。

たとえばあなたはウェブサイトを開いて表示させただけだと思うかもしれない。 だが、実際にはその裏では、まず電気を使ってメモリを確保し、今走っているプロセスを複製し、オープンシステムコールを呼び出してファイルを開き…といった動作をしている。 以下が、実際に私がVivaldiでTwitterを開いたときのコンピュータの動作を記録したものの、先頭の100行である。ちなみに、全体では3092行ある。

execve("/home/haruka/bin/web", ["web", "tw"], 0x7ffc1b63b5e8 /* 70 vars */) = 0
brk(NULL)                               = 0x55b1a8fd0000
arch_prctl(0x3001 /* ARCH_??? */, 0x7fffe7a03fa0) = -1 EINVAL (無効な引数です)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (そのようなファイルやディレクトリはありません)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=262914, ...}) = 0
mmap(NULL, 262914, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f90d5d77000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/libruby.so.2.7", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@\320\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=3389912, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f90d5d75000
mmap(NULL, 3501400, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f90d5a1e000
mprotect(0x7f90d5a4b000, 3170304, PROT_NONE) = 0
mmap(0x7f90d5a4b000, 2158592, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2d000) = 0x7f90d5a4b000
mmap(0x7f90d5c5a000, 1007616, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x23c000) = 0x7f90d5c5a000
mmap(0x7f90d5d51000, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x332000) = 0x7f90d5d51000
mmap(0x7f90d5d5b000, 105816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f90d5d5b000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`r\2\0\0\0\0\0"..., 832) = 832
lseek(3, 64, SEEK_SET)                  = 64
read(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784) = 784
lseek(3, 848, SEEK_SET)                 = 848
read(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32) = 32
lseek(3, 880, SEEK_SET)                 = 880
read(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0003\321\363P\3617(e\35t\335*V\272\321\344"..., 68) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2149496, ...}) = 0
lseek(3, 64, SEEK_SET)                  = 64
read(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784) = 784
lseek(3, 848, SEEK_SET)                 = 848
read(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32) = 32
lseek(3, 880, SEEK_SET)                 = 880
read(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0003\321\363P\3617(e\35t\335*V\272\321\344"..., 68) = 68
mmap(NULL, 1860536, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f90d5857000
mprotect(0x7f90d587c000, 1671168, PROT_NONE) = 0
mmap(0x7f90d587c000, 1363968, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f90d587c000
mmap(0x7f90d59c9000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x172000) = 0x7f90d59c9000
mmap(0x7f90d5a14000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bc000) = 0x7f90d5a14000
mmap(0x7f90d5a1a000, 13240, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f90d5a1a000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/libz.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 0\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=100096, ...}) = 0
mmap(NULL, 102416, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f90d583d000
mprotect(0x7f90d5840000, 86016, PROT_NONE) = 0
mmap(0x7f90d5840000, 57344, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7f90d5840000
mmap(0x7f90d584e000, 24576, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x11000) = 0x7f90d584e000
mmap(0x7f90d5855000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x17000) = 0x7f90d5855000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360\201\0\0\0\0\0\0"..., 832) = 832
lseek(3, 824, SEEK_SET)                 = 824
read(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\213\4\321\202[c\331\246\0\343\325z\307\20X\223"..., 68) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=159824, ...}) = 0
lseek(3, 824, SEEK_SET)                 = 824
read(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\213\4\321\202[c\331\246\0\343\325z\307\20X\223"..., 68) = 68
mmap(NULL, 135592, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f90d581b000
mmap(0x7f90d5822000, 65536, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x7000) = 0x7f90d5822000
mmap(0x7f90d5832000, 20480, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x17000) = 0x7f90d5832000
mmap(0x7f90d5837000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b000) = 0x7f90d5837000
mmap(0x7f90d5839000, 12712, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f90d5839000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/librt.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\3606\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=39408, ...}) = 0
mmap(NULL, 43512, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f90d5810000
mmap(0x7f90d5813000, 16384, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7f90d5813000
mmap(0x7f90d5817000, 8192, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x7000) = 0x7f90d5817000
mmap(0x7f90d5819000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x8000) = 0x7f90d5819000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/libgmp.so.10", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\0\1\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=596264, ...}) = 0
mmap(NULL, 598528, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f90d577d000
mmap(0x7f90d578d000, 430080, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x10000) = 0x7f90d578d000
mmap(0x7f90d57f6000, 94208, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x79000) = 0x7f90d57f6000
mmap(0x7f90d580d000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x8f000) = 0x7f90d580d000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\22\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=14512, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f90d577b000
mmap(NULL, 16528, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f90d5776000
mmap(0x7f90d5777000, 4096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1000) = 0x7f90d5777000
mmap(0x7f90d5778000, 4096, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f90d5778000
mmap(0x7f90d5779000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f90d5779000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/libcrypt.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360\22\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=43264, ...}) = 0
mmap(NULL, 233816, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f90d573c000
mprotect(0x7f90d573d000, 36864, PROT_NONE) = 0
mmap(0x7f90d573d000, 24576, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1000) = 0x7f90d573d000
mmap(0x7f90d5743000, 8192, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x7000) = 0x7f90d5743000
mmap(0x7f90d5746000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x9000) = 0x7f90d5746000
mmap(0x7f90d5748000, 184664, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f90d5748000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\363\0\0\0\0\0\0"..., 832) = 832

あなたはこれらを全て把握し、意図した上でウェブを見ているだろうか?

コンピュータの動作を意図するなど、馬鹿げているとは思わないだろうか。

広告は見えてるから意図どおり?

最近の広告は本当に重い。何十MBという通信容量をもっていき、なおかつ普通にプログラムを実行していてもなかなかお目にかかれないほどの計算リソースを電力と共に消費するものがたくさんある。

広告は単に表示領域をとって見せているだけではない。 それと同時に個人の行動を追跡したり、その他様々な行いをしている。だが、それらは目に見えない。

そもそも、広告の表示自体がそうした行動を追跡した結果によってなされている。 つまり、広告を表示する 前に あなたが何者で、どんなサイトをみていたかなどの情報を(無断で計算して無断で)送信し、それに基づいて広告を配信している。 そして、広告は表示して終わりというわけでもないのだ。

何十MBという通信量を消費する広告は、100回表示すればスマートフォンのその月の通信が終わってしまうようなものである。

さて、それは「意図した通りの」ことなのだろうか。

トラッカーは当たり前?

さて、ウェブサイトには当たり前のようにそうした行動を追跡したり、アクセスを分析したりするためのツールが埋め込まれ、さらに情報を求めてきたユーザーに華麗なアニメーションを見せたりしている。

これは、個人情報を送信されたり、計算資源を消費されていたり、不必要なものを見せられたり、待たされたりしている。 だからCoinhiveを取り立てて問題であるとするのは無理があるのだが、これについて、「それが当たり前だから全てのウェブでそうしている」と主張する人がいる。

ちょっと待ってほしい。

うちのサイトは、徹底してトラッカーの排除、ユーザー体験に寄与しない機能の排除を行っている。 その誇りにかけて、そうでないサイトもあるんだと声を上げたい。