Baron Samedit(sudo, CVE-2021-3156)
ほぼ全Linuxに入るsudoで約10年潜んだヒープ溢れが、なぜ末尾バックスラッシュ1個でローカルroot昇格に化けるのかを、エスケープ処理のオフバイ境界から正確につかめる。
- CVE-2021-3156はsudoのヒープバッファオーバーフローでCVSS 7.8。1.8.2〜1.8.31p2と1.9.0〜1.9.5p1が該当し、一般ユーザーからrootへ昇格できた。
- sudoedit -sでは引数の再解析が省かれ、末尾が単独バックスラッシュの引数でfrom++が終端NULを越える。set_cmnd()のアンエスケープが確保済みヒープ外へ書き続けた。
- 修正はコピー境界の判定を正し、対象モードでこの経路自体を無効化した。教訓はC文字列処理の境界の脆さ、長期潜伏バグの怖さ、そして広く配布されるsetuidバイナリの重点監査。
何が起きたか(影響範囲・深刻度・CVSS)
2021年1月26日、Qualysが sudo のヒープバッファオーバーフローを公表しました。CVE-2021-3156、通称 Baron Samedit。CVSS v3.1 は 7.8(High、AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)で、攻撃元がローカルに限られる代わりに、既定設定のまま一般ユーザーがrootへ昇格できる点が深刻でした。標的が sudoers に登録されている必要すらありません。
該当バージョンは長大です。脆弱コードは2011年7月のコミットで入り、レガシー系の 1.8.2〜1.8.31p2、安定系の 1.9.0〜1.9.5p1 が影響を受けました。約10年潜伏したこと、そして sudo がほぼ全てのUnix系OSに標準搭載されることから、影響範囲は事実上「世界中のLinux/BSDワークステーションとサーバー」に及びました。Qualysは Ubuntu 20.04・Debian 10・Fedora 33 で実際にroot取得を実証しています。修正版は 1.9.5p2 として同日公開されました。
「Baron Samedit」は、修正が sudoedit 経路に関わることと、ハイチのブードゥーに登場する死者の精霊「Baron Samedi」を掛けた命名です。技術的な深刻さとは無関係の、発見者による通称です。
脆弱性の原理(なぜ起きるか)
sudo はシェルのメタ文字を安全に扱うため、コマンドライン引数中の特殊文字をバックスラッシュでエスケープします。中心にあるのが sudoers プラグインの set_cmnd() で、argv を連結して1本のヒープバッファ user_args に書き写す処理です。長さの見積もりと実際のコピーは概略こうなっています。
/* 1. 必要サイズを見積もって確保 */
for (size = 0, av = NewArgv + 1; *av; av++)
size += strlen(*av) + 1;
user_args = malloc(size);
/* 2. コピーしつつ、シェルメタ文字を \ でエスケープ */
for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
while (*from) {
if (from[0] == '\\' && !isspace((unsigned char)from[1]))
from++; /* エスケープ:次の1文字を素通し */
*to++ = *from++;
}
*to++ = ' ';
}
問題は「\ の直後の1文字を無条件に読み進める from++」にあります。引数の末尾が単独のバックスラッシュ(\ の次が終端の \0)だと、from[1] は文字列終端のNULですが、isspace('\0') は偽なので分岐が成立し、from++ が終端NULを飛び越えます。続く *to++ = *from++ がNULをコピーし、from はバッファ境界の外へ進みます。ループ条件 *from はもう終端を認識できず、隣接メモリを走査しながら書き込みを続け、確保済み user_args の末尾を超えてヒープを溢れさせます。攻撃者は末尾バックスラッシュを含む引数の内容と個数で、溢れる長さと上書きされるバイト列をかなり自由に制御できます。
では、なぜ長年発火しなかったのか。通常の sudo cmd args... 経路では、引数はこのアンエスケープの前段で一度パースされ、既存のエスケープが正規化されるため、末尾に生のバックスラッシュだけが残る状況が作れません。ところが MODE_SHELL(-s)または MODE_EDIT(-e / sudoedit)が立つと、コマンドは後でシェルに丸ごと渡す前提でこの再パースが省かれ、生の argv がそのまま set_cmnd() に届きます。結果、sudoedit -s '……\' のように末尾を単独バックスラッシュにした引数だけが、境界チェックの効かない from++ を踏ませられるのです。C言語の「NUL終端に依存し、ポインタ前進に上限を設けない」文字列処理の弱点が、モードの組み合わせという条件下で露出した、典型的なオフバイ境界の欠陥でした。
バグ自体はコピーループにありますが、発火には「再パースを飛ばすシェル/編集モード」と「末尾が単独バックスラッシュの引数」が同時に必要でした。片方だけの監査では見落としやすい、条件依存の潜伏バグの典型です。
エクスプロイトの流れ(概念レベル)
横にスクロール
以下は防御目的の概観にとどめ、そのまま動く攻撃コードは示しません。攻撃者の狙いは、溢れたバイト列で sudo プロセス内の重要データを書き換え、root権限のまま任意コードを実行させることです。
- 溢れの誘発:
sudoedit(実体はsudo -e)を-sや-iと併せて起動し、末尾が単独バックスラッシュになる引数を渡して境界外書き込みを開始する。 - 溢れ長の制御: 引数の長さと個数で
user_argsを超えて書き込む範囲を調整する。sudoは書き込む文字自体も攻撃者入力なので、隣接領域を狙った値で塗れる。 - ヒープ配置の調整(ヒープグルーミング):
sudoは環境変数・nsswitch.confの設定・ロケール(LC_*)・エラーメッセージ用バッファなど多数のヒープ確保を行う。これらのサイズを環境変数経由で操り、溢れの着地点に上書きしたい構造体(サービスハンドラのリストなど関数ポインタを含むもの)を並べる。 - 制御奪取: 溢れで書き換えた関数ポインタや構造体を通じ、
sudoが root で動作している間に攻撃者コードへ実行を移す。Qualysは複数の異なる上書き先で実証しており、単一の当たり所に依存しない汎用性の高さも危険度を押し上げた。
ASLR・スタックカナリア・NXは主にスタック破壊やアドレス予測を対象とする防御で、ヒープ上のデータ(関数ポインタを含む構造体)を攻撃者入力で塗り替える本件のような手口を単独で止めることはできません。だからこそ、そもそも境界外へ書かせないこと(メモリ安全性)が本質的な対策になります。
修正と対策
修正版 1.9.5p2 は、境界を正しく扱うようコピー処理を書き換えました。要点は次のとおりです。
| 観点 | 脆弱版(〜1.9.5p1) | 修正版(1.9.5p2〜) |
|---|---|---|
| 末尾 \ の扱い | from[1] が NUL でも from++ し終端を飛び越す | 終端 NUL を検出したらループを打ち切り、飛び越さない |
| 対象モードの経路 | MODE_SHELL/MODE_EDIT で生 argv がアンエスケープに到達 | 該当モードではこの連結・アンエスケープ経路自体を通さない |
| 書き込み境界 | user_args の末尾を超えて書き込み得る | 確保サイズ内に収まることを保証 |
即応の緩和策としては、パッチ適用が最優先ですが、それが間に合わない環境では sudo を一時的に無効化する、あるいは sudoedit からsetuidビットを外して昇格経路を塞ぐ運用が案内されました。根治はあくまでアップグレードです。なお本件は認証を回避するのではなく、認証後の内部処理を壊す欠陥なので、パスワード必須設定や NOPASSWD の有無では防げません。自分の環境が該当するかは、末尾バックスラッシュ入り引数を sudoedit -s に与えたときにクラッシュ(malloc(): ... 等のヒープ破壊エラー)ではなく使い方エラーが返るか、という無害な挙動差で判別できます。
教訓(一般化できる原則)
1つ目、C文字列処理の境界は本質的に脆い。NUL終端への依存とポインタ前進の無制限さが、1バイトの読み違いをヒープ破壊に直結させる。2つ目、テストが通り続けても潜伏バグは消えない。発火条件が「特定モード×特殊入力」の交差だと、通常経路のテストや監査をすり抜けて長期間残る。3つ目、広く配布されるsetuidバイナリは攻撃対象領域が桁違いに大きい。
Baron Samedit が突いたのは、sudo というあらゆるUnix系に入り、rootへ昇格するためだけに存在するsetuidバイナリでした。一台で成立する欠陥が世界規模の資産になる構図は、セキュリティにおける最小権限や攻撃対象領域の考え方、そしてOSの特権境界の設計そのものと直結します。実務的な含意は次の3点に集約できます。
- 境界チェックはループの不変条件として書く: 「
\を見たら次を読む」ではなく「次を読む前に終端でないことを必ず確かめる」。あるいは境界外書き込みが原理的に起きない言語・API(バウンズチェック付きスライス等)へ移すことで、この種の欠陥を丸ごと排除できます。 - 潜伏バグ前提で監査対象を絞る: 全コードを均等に見張るのは非現実的です。
sudoのように「特権の分水嶺になる少数のバイナリ」を重点対象に据え、ファジングや静的解析を継続的に当てる方が費用対効果が高い。実際、本件も体系的なコードレビューで発見されました。C言語のメモリ破壊全般の力学はプログラミングの観点でも押さえておく価値があります。 - 投機的実行の欠陥(Spectre / Meltdown)と同様、「長く潜んでいた=安全」ではない: 広く使われ古くから存在するコードほど、まだ見つかっていないだけの前提で扱うのが安全側の構えです。
脆弱性の解剖の記事ガイド
Baron Samedit(sudo, CVE-2021-3156)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
sudo
比較で見る軸
難易度: advanced / カテゴリ: 脆弱性の解剖 / タグ数: 6
導入後に効く点
sudoedit -sでは引数の再解析が省かれ、末尾が単独バックスラッシュの引数でfrom++が終端NULを越える。set_cmnd()のアンエスケープが確保済みヒープ外へ書き続けた。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- 脆弱性の解剖
- タグ数
- 6
判断チェックリスト
- 自社の用途が「sudo / CVE」に近いか確認する。
- 強みである「CVE-2021-3156はsudoのヒープバッファオーバーフローでCVSS 7.8。1.8.2〜1.8.31p2と1.9.0〜1.9.5p1が該当し、一般ユーザーからrootへ昇格できた。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。