- 単純な機能にも何千もの依存関係と何千万行ものコードが動員される現実では、ソフトウェアの肥大化そのものがセキュリティ脆弱性の大きな原因になっている
- セキュリティはバグ密度だけでなく、攻撃者が到達できるコード総量にも左右され、不必要に広い攻撃面は実際の侵害につながりうる
- Electron JS、Node.js、Dockerイメージ、npm・PyPIの依存関係エコシステムは、配布コードの量と出所を不明瞭にし、ガレージのドアを開けるアプリにも5,000万行以上のアクティブコードが関与しうる
- Rust、sanitizer、fuzzerはコード品質を高めるが、文書内のコードを自動実行するような論理的な設計の失敗は、バグ除去だけでは防ぎにくい
- Trifectaは、新規コード1,600行、主要な依存関係約5個、合計3MB規模で画像共有機能を提供し、限られたコードと依存関係でも現代的なソフトウェアを作れることを示している
ソフトウェアセキュリティが危険な状態
- 最近のソフトウェアセキュリティの状況は非常に悪い
- Ivanti、MOVEit、Outlook、Confluence、Barracuda Email Security Gateway、Citrix NetScaler ADC・NetScaler Gatewayのような業界標準ソフトウェアで、過去1年間に深刻な侵害事例が発生した
- AppleやGoogleのようにリソースの豊富な企業でさえ、顧客を危険にさらすセキュリティ上のミスを犯している
- ソフトウェアがあまりに危険になったという認識から、自分で実行せずX as a serviceやクラウドに任せるべきだという助言が一般化している
- クラウドが脆弱なソフトウェアを信頼可能にするという前提も揺らいでいる
- Microsoftのメールプラットフォームは、機密の政府メールを含めてハッキングされた
- Azureクラウドのセキュリティには懸念が残っている
- Oktaは2年以内に2度目の侵害を受け、その後もOkta利用者のハッキング事例が不自然なほど続いた
- EUはソフトウェアセキュリティに対処するため、3つの立法を進めている
脆弱性はコード品質とコード量の両方から生まれる
- ソフトウェアセキュリティは2つの軸に依存している
- ソースコード内のセキュリティ問題の密度
- ハッカーがアクセス可能なコード量
- コードが多くなるほどリスクも高まる
- バグ密度が低くても、数百万行のコードの中から悪用可能な穴を見つけられる
- iMessageの事例は、攻撃面の拡大がどんな問題を生むかを示している
- 望まないiMessageでもiPhone上で即座に処理され、プレビューが生成される
- Appleはさまざまな画像形式をサポートしており、奇妙な圧縮フォントを含むPDFまで処理対象に入っていた
- その古い形式には実質的にプログラミング言語が含まれており、攻撃者はそれを通じて端末の別の弱点を探れた
- Appleは、プレビューをはるかに少ない画像形式、あるいは単一の「既知の安全な」形式に制限することで攻撃面を減らせたはずだ
- EU Cyber Resilience Actでも、ベンダーは攻撃面を最小化しなければならないと明記されている
より良いコードだけでは十分ではない
- コード品質を高めようとする動きはすでにある
- Rustのようなメモリ安全な言語
- AddressSanitizerのようなセキュリティ強化ツール
- 入力を自動変形して脆弱性やバグを見つけるfuzzer
- しかし、多くのセキュリティ問題はコード自体のバグよりも、その下にあるロジックから生じる
- Barracudaのメール脆弱性は、ウイルス検査のためにExcelスプレッドシートをスキャンしていたサードパーティ製ライブラリが、実際にはコードを実行していたことに起因していた
- 文書内のコードを自動実行する機能を入れるという判断は、コード中のバグをすべて取り除いても解決しない
何を配布しているのかわからない状態
- 現代のソフトウェアは大きくなりすぎて、実際に何を配布しているのか把握しにくい
- Niklaus Wirthは1995年の「A Plea for Lean Software」で、ソフトウェアがメガバイト級に膨らんだと批判していた
- 彼のOberonオペレーティングシステムは、エディタとコンパイラを含めて200KBだった
- 今では設定ファイルだけで200KBを超えるプロジェクトもある
- 今日の典型的なアプリはElectron JSベースで作られることがある
- Electron JSはChromiumとNode.jsを含む
- Node.jsは何万ものJavaScriptパッケージにアクセスできるようにする
- Electron JSを使うだけでも、依存関係込みで最低5,000万行のコードが入ると推定される
- アプリは数百から数千の補助パッケージを取り込み、依存関係がさらに依存関係を呼び込む
- ビルドに何が正確に含まれるかは日々変わりうる
- 一部のパッケージは、デフォルトで利用者を広告主やデータブローカーにさらす可能性がある
- 家の中の機器を制御するアプリは、Amazon側のソフトウェアスタックにも接続され、そのスタックもNode.jsと多数の依存関係を使っているかもしれない
- 結果として、ガレージのドアを開けるだけでも、複数のOSイメージと複数のサーバー上で5,000万行を超えるアクティブコードが動作しうる
コンテナと依存関係サプライチェーンの負担
- 以前はコンパイラの出力物やインタープリタで実行されるファイル群を配布しており、インストールや設定の過程でパッケージに何が含まれているか考える必要があった
- 今では、ソフトウェアだけでなく実行環境を合わせるためのOSファイルまで、コンテナで一緒に配布することが多い
- 実質的には完全なコンピュータのディスクイメージを配布しているような状況がしばしば起きる
- Docker Hubには350MBを超えるイメージが数多くある
- コンテナは有用に使えるが、実際の使い方次第では配布されるコード量を大きく増やしうる
- 依存関係のセキュリティ更新が最終アプリまで届くかどうかも不確かだ
- GoogleやAppleが急いで配布した画像処理バグの修正が、Electronアプリに今も残っているのか把握しにくい
- npmエコシステムには、パッケージリポジトリの乗っ取り、ハイジャック、同名パッケージの復活といった履歴がある
- PyPIも同様の問題を抱えている
- 依存関係はレビューが必要だが、何千ものものを頻繁に確認し続けることを期待するのは難しい
- だからといって、すべてを自前で再実装するのも答えではなく、SQLiteのように自作より安全である可能性が高い優れたモジュールもある
Trifecta: 小さなコードで作った画像共有ツール
- Trifectaは、ミニマルでありながら実際に使えるスタンドアロンの画像共有ソフトウェアである
- ブラウザで画像をドラッグ&ドロップして簡単に共有できる
- imgurを使うとブラウザに多くのCookieやトラッカーが入れられ、共有画像を見る人にもトラッカーが強制される可能性がある
- セルフホスト型の画像共有ツールも大規模フレームワークベースのものが多く、信頼しにくいと判断した
- Trifectaは、コード全体を数時間でレビューできるよう小さく作られている
- 新規ソースコード1,600行
- 重要な依存関係は約5個
- コード全体の規模は3MB
- 比較対象として挙げられた別の画像共有ソリューションは、288MBのDockerイメージで配布されている
- 別のNodeベースの写真共有ソリューションは、1,600個の依存関係と400万行以上のJavaScriptを持つことがわかった
- Trifectaは、不特定多数が画像をアップロードする公開サイト向けではなく、企業や個人利用に適している
複雑さを力と取り違える問題
- Trifectaに対するよくある反応の1つは、Amazon Web Services一式を使ってデプロイすべきだというものだった
- 外部サービスに依存しないスタンドアロンソフトウェアという目標に合わない反応である
- Dockerを不当に扱っているという反応もあったが、コンテナを良い目的に使える点は認められている
- Niklaus Wirthは1995年の論文で、人々が複雑さを洗練さと誤解する傾向を指摘していた
- Tony Hoareの言葉を借りれば、ソフトウェア設計には2つのやり方がある
- プログラムをあまりにも単純にして、誤りが明白に存在しないようにする方法
- あまりにも複雑にして、明白な誤りがないように見せる方法
- Wirthは、時間的プレッシャーが肥大化したソフトウェアの主要因だと見ていた
- 時間的プレッシャーは慎重な計画を妨げる
- 許容可能な解決策を改善する代わりに、素早い追加や修正へと向かわせる
- エンジニアの品質基準や完成度を徐々に損なう
- ソフトウェアの爆発的増大は自然法則ではなく、ソフトウェアエンジニアが減らすべき対象である
コード量を減らすことがセキュリティ対策になる
- 現在の世界は、あまりにも多くのコードを配布している
- その大半はサードパーティ製コードである
- 一部は意図せず含まれている
- 大半は十分に検査されていない
- その結果、膨大な攻撃面が生まれ、その中に平凡な品質のコードが大量に露出している
- コード品質改善の努力は続いているが、多くの悪用はロジックの失敗から生じており、それを検出する進展は相対的に少ない
- 世界に露出させるコード量を減らすだけでも大きな改善が見込める
- 製品の市場投入時間は延びるかもしれないが、今後の立法はベンダーにセキュリティをより真剣に扱わせる可能性がある
- TrifectaとOberonは、限られたコードと依存関係でも多くの機能を提供できることを示している
1件のコメント
Hacker News のコメント
Vernor Vinge の『A Deepness in the Sky』では、人類は亜光速技術だけで星々の間に広がっており、星間船には複数の恒星系や文明の古い技術が入り混じった状態で登場する。
コンピューターシステムもあまりに長く進化してきたため、コードの大半はもはや誰にも理解されず、ただ使われ、その上にさらに積み重ねられている。
とりわけある人物は、休眠状態と旅を長く繰り返してきた、生きている人間の中でも最古参に近い昔のシステムエンジニアなのだが、誰もがその上に何層も積み上げた未来では、むしろ彼の時代の動作方式や脆弱性を知っていることが大きな利点になる。
Vinge は何かを的確に言い当てていたのだと思う。
前者は現代科学と非常に賢い人々が解くべき本物の謎だが、後者は関心不足に近い。
十分なお金を払えば、有能なエンジニアが洗濯機を分解して正確な欠陥を突き止めるだろうが、誰もその費用を払わず、ただ捨てて新しく買うはずだ。
過去のソフトウェア知識は明らかに後者に属する。どの部分でも掘り下げれば最終的には完全に理解できるが、たいていは無視するか、さらに別の層を重ねるほうがはるかに安く実用的だ。
未来の人類が基本的な算術を忘れてしまい、誰かがそれを再発見すると権力者たちが戦争に利用しようとする話だ。伝えたいメッセージは分かるが、設定があまりにも非現実的で滑稽なので、力を失っていると思う。
さらに議論するなら http://lambda-the-ultimate.org/node/4424 を参照。
私たちはすでにこうした問題に直面している。60代の叔父は COBOL で書かれた古いトラック輸送ソフトウェアを保守していて、こういう旧式技術の仕事もある。興味があれば紹介できる。
根本的な問題は left-pad 事件と同じだ。監督なしのジュニアエンジニアが依存関係をいい加減にインストールすると笑うが、数十年と複数世代の開発者を経ると、結局ほとんどのソフトウェアはある程度、正体の分からない依存関係に頼ることになる。
2100年にアップデートを配布しなければならないとしよう。その時代の npm 依存関係管理システムを通じて押し込むことになるだろう。同時に、セキュリティアップデートを必要とする依存デバイスが太陽系規模で存在し、兆単位のデバイスと、最新状態かどうか分からない中間キャッシュがあるかもしれない。そんな依存関係ツリーがどんな姿になっているのか、想像もつかない。
核心にたどり着くためにフレームワークのコードを掘り下げなければならない環境で働くのももどかしい。時間を無駄にしている気がする。
肥大化は npm の大半のライブラリに見られる。作者が良い設計を知らず、すべてのライブラリに何でもやらせようとしている
文字列エンコーディング変換ライブラリだと言いながら、ファイルの読み込み、保存、インターネットからのダウンロード、コマンドラインツールまで同じリポジトリに入れるようなものだ。ライブラリは自分の仕事を一つだけ行い、残りはユーザーに任せるべきだ
Rust 側も良く見えるわけではない。Rust のドキュメントを直そうとすると、クレートが1000個ほどインストールされるのを目にする
問題は言語ではなく、誰でもライブラリを公開でき、実際に誰もが公開していることにある。「とにかく仕事を終わらせたい」人たちは機能が最も多いライブラリを選び、ライブラリの外で3行書けば済むコードを書きたくないために、さらに多くの機能を求める。「PDF レンダリングも追加できますか?」といった具合だ
解決策はよく分からないが、Low Dependency を支持するグループとバッジを作り、ライブラリ作者がそのバッジを欲しがり、ユーザーもライブラリを選ぶときに探すようにする案を考えている
逆に依存関係の少ないライブラリが欲しいなら、多くのことをする少数のものを取り込むことになる
私の見方では、信頼できる作者が作った、依存関係の少ない少し厚めのライブラリのほうが良いと思う。Lodash は大きいが ES6 モジュール版はツリーシェイキングをサポートしており、実質的に JavaScript になかった標準ライブラリの役割を果たしている。date-fns も Date について似た存在だ。JavaScript のコアライブラリの隙間を埋めるために、ほぼすべてのプロジェクトでこの二つをデフォルトで入れている
以前 Ruby on Rails の契約仕事をしたことがあるが、性能問題があまりにひどく、リリースモードで開発していた。サーバーがファイル変更を検知して自動リロードできないレベルだった
ある日うんざりして掘り下げ始めたが、いくつの gem を取り込んでいたのか思い出せないほど多かった。そのうちの一つは本当に3行のコードを省くための gem だった
それ以来 RoR コミュニティから距離を置いた。最近、何年ぶりかにまた RoR の契約を受けたが、昔ほどひどくはないにせよ、いまだに良くはない
依存関係がもたらすリスクをまったく尊重しないコミュニティもある
一方で、どんな Git リポジトリでも Go ライブラリをホストでき、誰でもその URL から使える点は本当に便利だ
まずパッケージ作者たちがこれをキャリアにしたがっており、注目を得る方法は大量のパッケージを作ることだけだ。だから自分が作った別のパッケージに依存するパッケージを作り続け、有用な一つ二つのパッケージを他人のコードに入れさせようとする
二つ目は、問題解決には常に新しいパッケージが含まれると信じている人たちだ。依存関係がどれほど多いか、実際の問題が難しいのかにも関心がない。だから問題の解き方を学ぶ代わりに、GitHub スター4個のラッパーの API を学ぶ
理想的には、こうしたツールが退屈な時間をかけて不要なパッケージを刈り込み、各パッケージの責務を最小化し、環境を合理的に整理したうえで独立してキャッシュし、LLM への依存をなくすべきだ。問題が起きたときだけアップデートのチェック担当者やキュレーターを呼ぶ形がよい
正直、現代ソフトウェアの最悪の問題の一つで、プロジェクトの50%以上を使い物にならなくしていると思う。退屈だが解ける問題なので、まともなLLM エージェントにはぴったりで、あれば大いに助けになるはずだ
「現代の飛行機を見たことがあるだろうか。そのラインが年々どのように進化しているかを追ったことがあるだろうか。飛行機だけでなく、人間が作るあらゆるものについて、人間の産業的努力と計算、図面の上で過ごした夜々が、結局は唯一で支配的な原理である究極の単純さへと帰結するのだと考えたことがあるだろうか?
まるで自然法則があり、家具の曲線や船の竜骨、飛行機の胴体を、人間の胸や肩の曲線のような原初的な純粋さに近づくまで磨き上げるには、何世代もの職人たちの実験が必要だと命じているかのようだ。完璧とは、もはや付け加えるものがなくなったときではなく、もはや取り除くものがなくなったときに到達するようだ。」
— Antoine de Saint Exupéry, Terre des Hommes
「ガレージのドアを開けるのに、5000万行を超えるアクティブなコードと複数サーバーの OS イメージが必要になることもある」と書いてみると、本当に狂ったことのように感じる
今この文章を打っているマシンでどれほど多くのコードが動いているのかを考えると、気が遠くなる。見直したこともなく、おそらく厳格なレビューもほとんど受けていないコードだ
まあ、また npm の依存関係をインストールしに行くよ
「ソフトウェアは今や、人々が自分で実行してはいけないと言われるほど危険なものと見なされている。代わりに『X as a service』提供者や、単に『クラウド』に任せろと言われる。自動車があまりに頻繁に燃えるので、自分で運転せず、専門の消防士が常に付き添うプロに運転を任せろという助言が出る架空の状況と比べてみてほしい」という比喩は、使わせてもらいたいくらい
元恋人は、かつての東側諸国で育ったというもっともな理由から「クラウド」を信用していなかった。だが代替案は、最安値で買ったHPノートPCをなくさないことを祈るだけだった。少し教えたあと、少なくともその点では安心できるようになった。
問題は、全般的な教育不足と、その結果への配慮不足だ。結局はリスクを受け入れるか、自分で学ぶか、SaaSやクラウド企業に頼るしかない。多くの涙を見てきたが、自分で学ぶケースはあまり見ていない。
個人の責任の問題なのに、誰もその責任を負おうとしないので、専門家に任せるほうが自分を信じるよりはましな解決策かもしれない。正解は教育だが、絶望的に難しい
ソフトウェアはこれ以上スリムにはなれない。そのためには時間、スキル、高額な人材が必要で、12種類の異なる技術スタックの例をつなぎ合わせてフランケンスーツを作る人だけでは無理だ。
私は独立開発者だが、昨年 node.js を覚えた人が、node.js、コンテナ、何かしらのAWSホスティングDBサービス、Lambda、オブジェクトストレージ、Cloudflare、YAML、React、Vite、その他の依存関係を1日で組み合わせ、型にはまっているが依然として脆弱なWebアプリを作るほうが、いつも私より安い価格を提示する。
スリムで高速で、実行コストが低く、保守もより安いソフトウェアは、長期的にはより安価でも、利益を出して書くのは難しい
昔は、システムが提供する標準フックやルーチンを、皆がインターフェースなどに使うという夢があった。Macintosh Toolbox や QuickDraw のようなものを思い浮かべればよい。
開発者の主な仕事はプログラムロジックを書くことで、変更や追加は透過的であるべきだと言われていた。システムコールは内部コードが変わっても同じ作業を滑らかに実行し、新機能は既存機能の上位集合なので、古いコードは問題なくコンパイルまたは実行されつつ、新しいソフトウェアはより多くの能力を得る、という形だった。
こうすれば保守が容易になり、インターフェースが標準化され、システムコールに多く依存するのでコードもスリムになると考えられていた。外部ライブラリは避けるべきだという雰囲気だった。
この夢はすぐに崩れた。DLLを思い浮かべればよい。今の多くのパッケージ管理やパッケージングは、正しいライブラリが存在することを保証する作業に近いように見える。
当時は大規模ソフトウェア開発がまだ幼年期に近かったのだから、期待通りにならなかったのも理解できる。今ではこうした問題についての集団的経験がかなり蓄積されているが、この夢は正気では実現不可能だという結論に至ったのか、それとも現在のごちゃごちゃした状態を十分に経験したおかげで、現代的な再挑戦へ向かっているのか気になる。
高速でスリムで安定し、安全なソフトウェアを望むなら、4つを同時にすべて得るのは難しいとしても、現状がその方向に向かっているのかはよく分からない
初期のLispは言語にごく少ないものしか入れなかったが、Rakuはnpmライブラリに行きそうな些細なものまで言語仕様に押し込む。
Cはコードをどうビルドするかを自分で決めさせたが、新しいコンパイル言語の多くは何らかの形でビルドツールを同梱している。
この地形ではかなり効果的なこともあるが、Wirthの仕事を導いた「あなた、機械、新しいプロジェクト」という文脈の外で行われる。問題は、データベースやブラウザエンジンのような巨大な依存関係の敷居を伴いがちで、その依存関係が作られたやり方を気に入らなければ結局不幸になる、という点にある
Rustについて私がずっと言っているのはこれだ。
古いC++の脆弱性の70%が本当にメモリ関連なら、コード1行あたりの脆弱性はC++より70%少ないかもしれない。
しかしRustで数百個のパッケージを引き込み、コード行数が10倍になるなら話は変わる。
10万行の30%は、1万行の100%より総量が大きい
QTのようなものもRustで書かれていたなら、それ自体が数百個のクレートになっていただろうが、コード量と引き受けるリスク水準はまったく同じだったはずだ
しかし大きな問題は脆弱性だ。共有ライブラリ1つのバグを直して数百個のライブラリを直すほうがよいのか、それとも数百個のライブラリを1つずつ直すほうがよいのか?
Rustが、いくつかの巨大な依存関係の代わりに多くの小さな依存関係を簡単に引き込めるようにしているという事実は関係ない。より多くのコードを書くわけではない。
たとえばRustの
regexクレートを依存関係として数えるのか? C++ではそれは標準ライブラリに入っている。BoostをC++で依存関係1つとして数えるのか? Rustならそれは別々のクレート30個ほどに相当するだろう
RustプログラムでC++に比べてリモートコード実行がどれほどあっただろうか? C++のリモートコード実行の頻度は、Rustより70%をはるかに超えて多いと思う
最近のアプリはたいてい Electron JS で作られていると言われますが、各プラットフォームのネイティブな Web コントロールを使い、Electron を同梱しないこともできる、という点は十分に知られていない気がします
そうすれば、配布するアプリはキロバイト単位にできます。このアプローチなら、Web ビューと通信できさえすれば、どんなバックエンド言語や技術スタックを使うかは自由です
今なら、かなり大きな割合のアプリケーションで PWA が可能であるべきではないでしょうか?
よくは分かりませんが、Discord も Electron アプリではなく PWA になれるのでは?
最大の穴は、SQLite に相当する強力な何かと IndexedDB との差でしょうが、それでもほとんどのアプリは IndexedDB の B-tree モデルより高水準のクエリ言語を必ずしも必要としない気がします
suckless 哲学にもう一度喝采を。万歳
[0 ]https://suckless.org/