How my minimal, memory-safe Go rsync steers clear of vulnerabilities

Michael Stapelberg

私の最小限でメモリセーフなGo製rsyncが脆弱性を回避する仕組み

原文は Michael Stapelberg により に公開されました。 このブログを購読する

2025年1月、複数のセキュリティ研究者が合わせてrsyncにおける6件のセキュリティ脆弱性を公開しました。中には任意コードの実行やファイル漏洩を可能にするものもあり、当然ながら自分のgokrazy/rsync実装が影響を受けるのか、受けるとすればどのように影響するのかが気になりました。モダンでメモリセーフなプログラミング言語であるGoで、(互換性は保ちつつも最小限の)自前のrsyncを実装したことで、本当に特定のクラスのセキュリティ脆弱性を丸ごと排除できたのだろうか?

この深掘り記事は2025年1月から執筆を進めていましたが、執筆過程で未公開の脆弱性がさらに見つかったため公開が遅れました!「セキュリティ脆弱性」セクションでは、2025年1月のバッチと2026年5月のバッチを合わせた全12件の脆弱性を扱っています。

本番環境で(アップストリームのSamba版)rsyncを運用している場合は、バージョン3.4.3以降にアップグレードしてください。

本番環境でgokrazy/rsyncを運用している場合は、バージョンv0.3.3以降にアップグレードしてください。

細かい脆弱性の詳細は飛ばして、直接次のセクションに進んでいただいてもかまいません:

背景: 自作のrsync

背景として、2022年6月にrsyncやその使い方、仕組みについてブログを書きました「rsync」タグが付いたすべての投稿もご覧ください。

自前のrsyncを書いた当初の動機は(当時はサーバーのみでしたが、現在はあらゆる方向での転送に対応しています)、高速なパッケージ管理を目指すLinuxディストリビューション研究プロジェクトであるdistriのソフトウェアパッケージを提供することでした。これを自宅の小さなLinux+Go製インターネットルーターであるrouter7でホストしたかったのです。router7自体はGo製アプライアンスプラットフォームであるgokrazy上に構築されています。

今でもこの当初の目的のために複数のgokrazy/rsyncサーバーを運用していますし、それ以外にもたくさん動かしています!rsyncをプリミティブとして利用できる(Goプログラムにリンクできる!)のは本当に便利です。

セキュリティ脆弱性

この記事では、以下のセキュリティ脆弱性を扱います:

  • CVE-2024-12084から12088 (オリジナルのレポート)
  • CVE-2024-12747(Aleksei Gorban「loqpa」氏により別途発見)
  • CVE-2026-29518(Damien Neil氏と私自身により発見! およびNullx3D氏により独立して発見)
  • CVE-2026-43617から43620
  • CVE-2026-45232

上記の脆弱性のうち最初のバッチはoss-securityメーリングリストでアナウンスされましたが、oss-securityの要約よりもオリジナルのレポートの方が詳細に書かれていることに注意してください!

その後の脆弱性はrsyncプロジェクトのGitHub Security Advisoriesを通じてアナウンスされました。

2025年1月のバッチ

CVE-2024-12084: ヒープバッファオーバーフロー (9.8)

概要:

  • rsyncは検証が不十分でした。ネットワークから(攻撃者が制御可能な)チェックサム長を読み取り、その長さをMAX_DIGEST_LENと比較していました。
  • しかし、rsyncのデータ構造では常に16バイトのバッファが宣言されていました:char sum2[SUM_LENGTH]
    • SUM_LENGTHは常に16(バイト)で、MD4MD5のチェックサムを格納するには十分です。
    • MAX_DIGEST_LENは従来16(バイト)でしたが、rsyncがSHA256やSHA512チェックサム対応でコンパイルされるとより大きくなります。
  • そのため、境界チェックは効果がありませんでした!攻撃者は境界外に書き込むことができました。
  • この問題は、SHA256/SHA512チェックサム対応を追加した2022年9月のコミットae16850で導入されました。
クリックで不適切なチェックサム長検証の詳細な説明を展開(Google Securityレポートからの引用)

チェックサムがデーモンによって読み取られる際、2つの異なるチェックサムが読み取られます:

  1. 32ビットのAdler-CRC32チェックサム
  2. ファイルチャンクのダイジェスト。ダイジェストアルゴリズムはプロトコルネゴシエーションの冒頭で決定されます。対応するコードは以下の通りです: sender.c:
s->sums = new_array(struct sum_buf, s->count);
for (i = 0; i < s->count; i++) {
    s->sums[i].sum1 = read_int(f);
    read_buf(f, s->sums[i].sum2, s->s2length);

最も重要な点として、sum2フィールドはs->s2lengthバイトで埋められることに注意してください。sum2は常にサイズが16です: rsync.h

#define SUM_LENGTH 16
// …
struct sum_buf {
    OFF_T offset;           /**< offset in file of this chunk */
    int32 len;              /**< length of chunk of file */
    uint32 sum1;            /**< simple checksum */
    int32 chain;            /**< next hash-table collision */
    short flags;            /**< flag bits */
    char sum2[SUM_LENGTH];  /**< checksum  */
};

s2lengthは攻撃者が制御可能な値であり、次のスニペットが示すように最大でMAX_DIGEST_LENバイトになり得ます:

io.c

sum->s2length = protocol_version < 27 ? csum_length : (int)read_int(f);
if (sum->s2length < 0 || sum->s2length > MAX_DIGEST_LEN) {
    rprintf(FERROR, "Invalid checksum length %d [%s]\n",
            sum->s2length, who_am_i());
    exit_cleanup(RERR_PROTOCOL);
}

ここでの問題は、バイナリがどのダイジェスト対応でコンパイルされたかによってMAX_DIGEST_LENが16バイトより大きくなり得ることです:

md-defines.h

#define MD4_DIGEST_LEN 16
#define MD5_DIGEST_LEN 16
#if defined SHA512_DIGEST_LENGTH
  #define MAX_DIGEST_LEN SHA512_DIGEST_LENGTH
#elif defined SHA256_DIGEST_LENGTH
  #define MAX_DIGEST_LEN SHA256_DIGEST_LENGTH
#elif defined SHA_DIGEST_LENGTH
  #define MAX_DIGEST_LEN SHA_DIGEST_LENGTH
#else
  #define MAX_DIGEST_LEN MD5_DIGEST_LEN /* 16 bytes */
#endif

SHA256対応は一般的で、MAX_DIGEST_LENGTHの値を64に設定します。結果として、攻撃者はsum2バッファの制限を超えて最大48バイトまで書き込むことができます。

アップストリームの修正:

CVE-2024-12084に対するアップストリームの修正では、sum2フィールドをxfer_sum_lenの長さで確保される動的確保のsum2_arrayフィールドに変更し、境界チェックをこの転送のアルゴリズムのチェックサム長であるxfer_sum_lenに対するチェックに修正しています。

Goは防止に役立つか?

はい:Goでは、境界チェックの欠如や誤りがヒープバッファオーバーフローにつながることはありません!その代わり、Goランタイムが境界チェックを行うため、境界外への書き込みを試みるとパニックになります。

gokrazy/rsyncへの影響は?

gokrazy/rsyncでも検証が不十分でした!ただし問題は異なります。サイズの混同ではなく、単にsumヘッダーの検証をまったく行っていなかったのです — うっかりしていました!

Goランタイムの境界チェックが境界外への書き込みの試みで発動することは、コードを次のように変更してテストを実行することで確認できます:

diff --git i/types.go w/types.go
index 5601697..899fcb8 100644
--- i/types.go
+++ w/types.go
@@ -59,7 +59,7 @@ func (sh *SumHead) WriteTo(c *rsyncwire.Conn) error {
 	var buf rsyncwire.Buffer
 	buf.WriteInt32(sh.ChecksumCount)
 	buf.WriteInt32(sh.BlockLength)
-	buf.WriteInt32(sh.ChecksumLength)
+	buf.WriteInt32(512 /*sh.ChecksumLength*/)
 	buf.WriteInt32(sh.RemainderLength)
 	return c.WriteString(buf.String())
 }

予想通り、Goランタイムは次のメッセージでパニックします:

panic: runtime error: slice bounds out of range [:512] with length 16

goroutine 277 [running]:
github.com/gokrazy/rsync/rsyncd.(*sendTransfer).receiveSums(0xc0000d7b68)
	/home/michael/go/src/github.com/gokrazy/rsync/rsyncd/sender.go:136 +0x339
github.com/gokrazy/rsync/rsyncd.(*sendTransfer).sendFiles(0xc0000d7b68, 0xc000120820)
	/home/michael/go/src/github.com/gokrazy/rsync/rsyncd/sender.go:46 +0x134
github.com/gokrazy/rsync/rsyncd.(*Server).handleConnSender(0xc000476090, {{0x95ed9b, 0x7}, {0xc000426810, 0x2a}, {0x0, 0x0, 0x0}}, {0xa2a120, 0xc0000b2ba0}, ...)
	/home/michael/go/src/github.com/gokrazy/rsync/rsyncd/rsyncd.go:397 +0x26a
github.com/gokrazy/rsync/rsyncd.(*Server).HandleConn(0xc000476090, {{0x95ed9b, 0x7}, {0xc000426810, 0x2a}, {0x0, 0x0, 0x0}}, {0xa2a120, 0xc0000b2ba0}, ...)
	/home/michael/go/src/github.com/gokrazy/rsync/rsyncd/rsyncd.go:351 +0x37a
github.com/gokrazy/rsync/rsyncd.(*Server).HandleDaemonConn(0xc000476090, {0x94db80?, 0xc00018a040?}, {0x7fd15838b118, 0xc000428028}, {0xa2bd90, 0xc0002303c0})
	/home/michael/go/src/github.com/gokrazy/rsync/rsyncd/rsyncd.go:307 +0xdbb
github.com/gokrazy/rsync/rsyncd.(*Server).Serve.func2()
	/home/michael/go/src/github.com/gokrazy/rsync/rsyncd/rsyncd.go:450 +0xaf
created by github.com/gokrazy/rsync/rsyncd.(*Server).Serve in goroutine 260
	/home/michael/go/src/github.com/gokrazy/rsync/rsyncd/rsyncd.go:448 +0xd2

もちろん、サーバー全体をクラッシュさせるのは最良の失敗の仕方ではないので、不足していた境界チェックを追加してパニックをエラーに変えるようにしました。

CVE-2024-12085: スタックからの情報漏洩によるASLRの無効化 (7.5)

概要:

前のCVE-2024-12084と同じ検証不足のため、攻撃者は短いチェックサムを持つチェックサムアルゴリズム(例:8バイトのチェックサムを持つxxhash64)を選択しつつ、より長いチェックサム(例:9バイト)を送っていると主張することで、被害者にレスポンス内で初期化されていないスタック内容の1バイトを漏洩させることができました。

スタック内容の1バイト漏洩は一見無害に思えるかもしれませんが、Google Securityレポートが述べているように:

最初の2つの脆弱性はヒープバッファオーバーフローと情報漏洩です。組み合わせることで、クライアントはrsyncサーバーが動作しているマシン上で任意のコードを実行できます。クライアントに必要なのはサーバーへの匿名の読み取りアクセスだけです。

クリックで情報漏洩の詳細な説明を展開(Google Securityレポートからの引用)

デーモンは、クライアントがサーバーに送信したチャンクのチェックサムを、hash_search()内でローカルファイルの内容と照合します。関数プロローグの一部として、スタック上にMAX_DIGEST_LENバイトのバッファが確保されます:

static void hash_search(int f, struct sum_struct *s,
                        struct map_struct *buf, OFF_T len)
{
    OFF_T offset, aligned_offset, end;
    int32 k, want_i, aligned_i, backup;
    char sum2[MAX_DIGEST_LEN];

デーモンはクライアントが送信したチェックサムを反復処理し、各チャンクについてダイジェストを生成してリモートのダイジェストと比較します:

if (!done_csum2) {
    map = (schar *)map_ptr(buf, offset, l);
    get_checksum2((char *)map, l, sum2);
    done_csum2 = 1;
}

if (memcmp(sum2, s->sums[i].sum2, s->s2length) != 0) {
    false_alarms++;
    continue;
}

注目すべきは、比較されるバイト数が再びs->s2lengthバイトであることです。この場合、s->s2lengthは最大でMAX_DIGEST_LENになり得るため、比較が境界外になることはありません。

しかし、攻撃者が制御するs->sums[i].sum2と混同しないように注意が必要なローカルのsum2バッファは、スタック上のバッファでありクリアされておらず、初期化されていないスタック内容を含んでいます。

悪意のあるクライアントは、ファイルの特定のチャンクに対して(既知の)xxhash64チェックサムを送信でき、これによりデーモンはスタックバッファsum2に8バイトを書き込みます。攻撃者はその後s->s2lengthを9バイトに設定できます。このような設定の結果、最初の8バイトは一致し、攻撃者が制御する9バイト目が初期化されていないスタックデータの未知の値と比較されます。

攻撃者はファイルを255個のチャンクに分割でき、結果としてファイルのダウンロードごとに1バイトを漏洩させることができます。攻撃者は同じ接続内で、あるいは接続をリセットして、このプロセスを段階的に繰り返すことができます。

結果として、ヒープオブジェクトへのポインタ、スタッククッキー、ローカル変数やグローバル変数へのポインタ、リターンポインタを含み得る、初期化されていないスタックデータのMAX_DIGEST_LEN - 8バイトを漏洩させることができます。これらのポインタを使ってASLRを無効化できます。

アップストリームの修正:

関連するアップストリームの修正は2つあります:

Goは防止に役立つか?

はい:Goは設計上、すべての変数をゼロ値で初期化します。Goプログラマーは変数を明示的に初期化することを覚えておく必要がありません。

gokrazy/rsyncへの影響は?

gokrazy/rsyncはこの脆弱性の影響を受けません。Goでは変数は常に初期化されます。

加えて、MD4以外のチェックサムの選択はプロトコルバージョン30で初めて導入されましたが、gokrazy/rsyncはプロトコルバージョン27を実装しています。

CVE-2024-12087: シンボリックリンクを使ったパストラバーサル (7.5)

説明:Google Securityレポートからの引用)

シンボリックリンクの同期が-lまたは-a--archive)フラグによって有効になっている場合、悪意のあるサーバーはクライアントに宛先ディレクトリ外の任意のファイルを作成させることができます。悪意のあるサーバーはクライアントに次のようなファイルリストを送信できます:

symlink -> /arbitrary/directory
symlink/poc.txt

シンボリックリンクはデフォルトでは絶対パスであったり、../../のような文字列を含んだりすることができます。

実際には、クライアントはファイルリストを検証し、symlink/poc.txtエントリを見たときにsymlinkというディレクトリを探します。見つからなければエラーになります。サーバーがsymlinkを[ディレクトリとシンボリックリンクの両方として]送信した場合、[クライアント]はディレクトリエントリのみを保持するため、攻撃を成功させるにはさらに工夫が必要です。

inc_recurseモードでは、サーバーがクライアントに対して有効にできるモードですが、サーバーはクライアントに複数のファイルリストを送信します。エントリの重複排除はファイルリストごとに行われます。結果として、悪意のあるサーバーはクライアントに複数のファイルリストを送信でき、例えば:

# file list 1:
.
./symlink (directory)
./symlink/poc.txt (regular file)

# file list 2:
./symlink -> /arbitrary/path (symlink)

結果として、symlinkディレクトリが最初に作成され、symlink/poc.txtはファイルリスト内の有効なエントリと見なされます。その後、攻撃者はsymlinkの種別をシンボリックリンクに変更します。

サーバーがクライアントにsymlink/poc.txtファイルの作成を指示すると、シンボリックリンクを辿ることになり、宛先ディレクトリ外にファイルを作成できてしまいます。

Goは防止に役立つか?

いいえ。この脆弱性はロジックエラーが原因です。複数のファイルリストが使われる場合、マージされたファイルリストを再検証する必要があります。

ただし多層防御: Goのos.Rootを参照してください。

アップストリームの修正:

CVE-2024-12087に対するアップストリームの修正では、不足していた検証が追加されています。

gokrazy/rsyncへの影響は?

gokrazy/rsyncはこの脆弱性の影響を受けません。gokrazy/rsyncはインクリメンタル再帰モード(--inc-recursive)を実装していないからです。

ここでのトレードオフは実装の複雑さとリソース使用量です。インクリメンタル再帰モードでは、転送を開始する前にファイルセット全体をスキャンする必要がなく、ファイルセットを「ウィンドウ」的に扱うことができます。私のrsyncはどのように動くのか?というブログ投稿も参照してください。

CVE-2024-12088: --safe-linksのバイパス (7.5)

説明:Google Securityレポートからの引用)

--safe-links CLIフラグは、クライアントにサーバーから受け取ったシンボリックリンクを検証させます。期待される動作は、シンボリックリンクのターゲットが1)宛先ディレクトリからの相対パスであること、2)決して宛先ディレクトリ外を指さないことです。

unsafe_symlink()関数がこれらのシンボリックリンクの検証を担当します。この関数は、宛先ディレクトリ内の位置を基準としたシンボリックリンクターゲットのトラバーサル深度を計算します。

例えば、次のシンボリックリンクは安全でないと見なされます:

{DESTINATION}/foo -> ../../

宛先ディレクトリ外を指しているためです。一方、次のシンボリックリンクは依然として宛先ディレクトリ内を指しているため安全と見なされます:

{DESTINATION}/foo -> a/b/c/d/e/f/../../

この関数は、シンボリックリンクの宛先のパスに他のシンボリックリンクが含まれているかどうかを考慮しないため、バイパスされる可能性があります。例えば、次の2つのシンボリックリンクを考えてみてください:

{DESTINATION}/a -> . {DESTINATION}/foo -> a/a/a/a/a/a/../../

この場合、fooは実際には宛先ディレクトリ外を指すことになります。しかし、unsafe_symlink()関数はa/がディレクトリであると想定し、シンボリックリンクは安全であると判断します。

アップストリームの修正:

CVE-2024-12088に対するアップストリームの修正では、パスの先頭以外では../を一切許可しないようにすることでunsafe_symlink()をより厳格にしています。

Goは防止に役立つか?

いいえ。この脆弱性はロジックエラーが原因です。検証関数が正しくありませんでした。同じバグを私たちも実装していた可能性があります。

ただし多層防御: Goのos.Rootを参照してください。

gokrazy/rsyncへの影響は?

gokrazy/rsyncは影響を受けません。--safe-links機能はgokrazy/rsyncではまだ実装されていないからです。

CVE-2024-12086: 任意ファイル漏洩 (6.8)

概要:

rsyncのレシーバー(クライアントモード)は、rsyncセンダーから提供されたファイル名をサニタイズせず、宛先ツリー外のファイルを開くことを防止していませんでした。悪意のあるセンダーは、レシーバーに宛先ツリー外の任意のファイルのチェックサムを比較させることができました。提供された1バイトのチェックサムに対するレシーバーの反応を観察することで、悪意のあるセンダーは任意のファイルを漏洩させることができます。

クリックでファイル漏洩の詳細な説明を展開(Google Securityレポートからの引用)

クライアントが悪意のあるサーバーに接続すると、サーバーはクライアントマシン上の任意のファイルの内容を漏洩させることができます。read_ndx_and_attrs()において、クライアントはサーバーが適切なフラグを設定した場合、サーバーからfnamecmpタイプとxnameを読み取ります。sanitize_pathsフラグはクライアントが受信する際には設定されません。

if (iflags & ITEM_BASIS_TYPE_FOLLOWS)
    fnamecmp_type = read_byte(f_in);
*type_ptr = fnamecmp_type;

if (iflags & ITEM_XNAME_FOLLOWS) {
    if ((len = read_vstring(f_in, xname, MAXPATHLEN)) < 0)
        exit_cleanup(RERR_PROTOCOL);

    if (sanitize_paths) { /* not enabled when client receives */
        sanitize_path(xname, xname, "", 0, SP_DEFAULT);
        len = strlen(buf);
    }
} else {
    *buf = '\0';
    len = -1;
}
*len_ptr = len;

呼び出し元(recv_files())は、サーバーから提供された値を使って、受信データを比較するファイル(basis file)を決定します。

    case FNAMECMP_FUZZY:
    if (file->dirname) {
        pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, file->dirname, xname);
        fnamecmp = fnamecmpbuf;
    } else
        fnamecmp = xname;
        break;
…
fd1 = do_open(fnamecmp, O_RDONLY, 0);

receive_data()では、xnameで指定されたファイルの内容が宛先ファイルにコピーされます。これはサーバーが負のトークンを送信することで実現できます。

while ((i = recv_token(f_in, &data)) != 0) {
..snip..
    if (i > 0) {
..snip..
    }
..snip..
    if (fd != -1 && map && write_file(fd, 0, offset, map, len) != (int)len)

サーバーは比較用のチェックサムを送信します。一致しなければ0が返されます。

if (fd != -1 && memcmp(file_sum1, sender_file_sum, xfer_sum_len) != 0)
    return 0;

戻り値が0の場合、レシーバーはジェネレーターにMSG_REDOを送信します。ジェネレーターはその後サーバーにメッセージを書き込みます。

サーバーはこれを、送信したチェックサムが正しかったかどうかを判断するシグナルとして利用できます。blengthを1から始めることで、悪意のあるサーバーはターゲットファイルの内容を1バイトずつ特定できます。

アップストリームの修正:

CVE-2024-12086に対するアップストリームの修正では、センダーから提供されたパスを検証することで、宛先ツリー外のファイルを開くことを防止しています。

Goは防止に役立つか?

はい、Goはこれを防ぐためのAPIを提供しています。多層防御: Goのos.Rootを参照してください。

gokrazy/rsyncへの影響は?

gokrazy/rsyncは影響を受けません。fuzzyマッチング機能はrsyncプロトコルバージョン29で導入されましたが、gokrazy/rsyncはプロトコルバージョン27を実装しているからです。

CVE-2024-12747: シンボリックリンクの競合状態 (5.6)

説明:Red Hat Security Advisoryからの引用)

rsyncに欠陥が見つかりました。この脆弱性は、rsyncのシンボリックリンク処理における競合状態に起因します。シンボリックリンクに遭遇した際のrsyncのデフォルトの動作は、それらをスキップすることです。攻撃者が適切なタイミングで通常ファイルをシンボリックリンクに置き換えた場合、デフォルトの動作をバイパスしてシンボリックリンクを辿ることが可能でした。rsyncプロセスの権限によっては、攻撃者が機密情報を漏洩させ、権限昇格につながる可能性があります。

アップストリームの修正:

CVE-2024-12747に対するアップストリームの修正では、rsyncセンダー内のopen()呼び出しをO_NOFOLLOWオプションを使うように変更しています。アルゴリズムのその時点ではパスがシンボリックリンクであることは想定されておらず(シンボリックリンクはreadlink(2)で処理されるはずです)。

Goは防止に役立つか?

はい、Goはこれを防ぐためのAPIを提供しています。多層防御: Goのos.Rootを参照してください。

gokrazy/rsyncへの影響は?

gokrazy/rsyncはコミット1b1fbf6以前は脆弱でした。このコミットはアップストリームのrsyncと同じO_NOFOLLOWによる緩和策を導入しています。

クリックでgokrazy/rsyncで問題を再現する手順を展開

問題を再現するには、次の手順を使用します:

  1. gokrazy/rsync v0.2.7をチェックアウトします:

    git clone https://github.com/gokrazy/rsync
    cd rsync
    git checkout v0.2.7
  2. 次のようにコードにパッチを当てて修正を元に戻し、攻撃を実行します:

    diff --git i/internal/nofollow/nofollow_unix.go w/internal/nofollow/nofollow_unix.go
    --- i/internal/nofollow/nofollow_unix.go
    +++ w/internal/nofollow/nofollow_unix.go
    @@ -2,8 +2,6 @@
    
     package nofollow
    
    -import "golang.org/x/sys/unix"
    
     // Maybe resolves to unix.O_NOFOLLOW on unix systems,
     // 0 on other platforms.
    -const Maybe = unix.O_NOFOLLOW
    +const Maybe = 0 // unix.O_NOFOLLOW
    
    diff --git i/internal/sender/do.go w/internal/sender/do.go
    --- i/internal/sender/do.go
    +++ w/internal/sender/do.go
    @@ -2,6 +2,8 @@ package sender
    
     import (
     	"fmt"
    +	"os"
    +	"path/filepath"
     	"sort"
    
     	"github.com/gokrazy/rsync/internal/log"
    @@ -55,6 +57,15 @@ func (st *Transfer) Do(crd *rsyncwire.CountingReader, cwr *rsyncwire.CountingWri
     		st.Logger.Printf("file list sent")
     	}
    
    +	// HACK: swap out the passwd file with a symlink to /etc/passwd
    +	if err := os.Remove(filepath.Join(modPath, "passwd")); err != nil {
    +		return nil, err
    +	}
    +	if err := os.Symlink("../passwd", filepath.Join(modPath, "passwd")); err != nil {
    +		return nil, err
    +	}
    +	st.Logger.Printf("HACK: swapped passwd file for symlink")
    +
     	// Sort the file list. The client sorts, so we need to sort, too (in the
     	// same way!), otherwise our indices do not match what the client will
     	// request.

TestReceiverSymlinkTraversalテストを実行すると、サーバーがシンボリックリンクを辿ったことがわかります:

    receiver_test.go:371: unexpected file contents: diff (-want +got):
          bytes.Join({
        - 	"benign",
        + 	"secret",
          }, "")

驚くべき発見

この記事の草稿をGoセキュリティチームのメンバーであり、トラバーサル耐性のあるos.Root APIの作者でもあるDamien Neil氏と共有したところ、彼はこう指摘しました:

CVE-2024-12747に対するgokrazyの修正は不十分だと思います。os.OpenO_NOFOLLOW付きで呼び出していますが、O_NOFOLLOWはパスの最後のコンポーネントでのシンボリックリンクのトラバーサルのみを防止します。

これは、パスのより前のコンポーネントを置き換えることで依然として脆弱である可能性があります。例えばos.Open("dir/passwd")は、dir/etcへのシンボリックリンクにすることでリダイレクトできてしまいます。

私たちは2025年4月にこのことをrsyncのセキュリティ連絡先に報告しました。2025年12月に、他の誰かもこの問題を独立して発見し報告していたことを知りました。

最終的に、これは2026年5月20日に公開されたCVE-2026-29518につながりました。

2026年5月のバッチ

CVE-2026-29518: シンボリックリンクの競合状態 (7.0)

説明:rsync 3.4.3 NEWSエントリからの引用)

chrootなしのデーモンモードにおけるローカル権限昇格を可能にするTOCTOUシンボリックリンク競合状態。

use chroot = noで設定されたrsyncデーモンは、親パスコンポーネントに対するtime-of-check / time-of-use競合に晒されます。モジュールへの書き込みアクセスを持つローカル攻撃者は、レシーバーのチェックとopen()の間に親ディレクトリコンポーネントをシンボリックリンクに置き換えることで、モジュール外への読み取り(basisファイルの漏洩)や書き込み(ファイルの上書き)をリダイレクトできます。デーモンが高い権限で動作している場合、これは権限昇格を可能にします。

デフォルトのuse chroot = yesでは影響を受けません。

到達範囲:デーモンホスト上のローカル攻撃者、モジュールパスへの書き込みアクセス、use chroot = noで設定されたデーモン。

アップストリームの修正:

CVE-2026-29518に対するアップストリームの修正では、Goのos.Root APIに似たsecure_relative_open()を使用しています。

Goは防止に役立つか?

はい、Goはこれを防ぐためのAPIを提供しています。多層防御: Goのos.Rootを参照してください。

gokrazy/rsyncへの影響は?

gokrazy/rsyncは、センダーレシーバートラバーサル耐性のあるos.Root APIに切り替えるまで脆弱でした。

CVE-2026-43618: 整数オーバーフローによるリモートメモリ漏洩 (8.1)

説明:GitHub Security Advisoryからの引用)

説明: レシーバーの圧縮トークンデコーダーは、オーバーフローチェックなしで32ビット符号付きカウンタを累積していました。悪意のあるセンダーは、巧妙に操作することでオーバーフローを引き起こし、プロセスメモリの内容を攻撃者に漏洩させることができます — 環境変数、パスワード、ヒープやライブラリのポインタなどです — これによりASLRが大幅に弱体化し、さらなる攻撃が容易になります。

到達範囲:圧縮が有効な認証済みデーモン接続(両方のピアがアドバタイズしている場合、プロトコル>= 30ではデフォルトで有効)。デーモン側で圧縮を無効にする(rsyncd.confで「refuse options = compress」)ことが利用可能な回避策です。

アップストリームの修正:

CVE-2026-43618に対するアップストリームの修正では、不足していたチェックが導入されています。

gokrazy/rsyncへの影響は?

gokrazy/rsyncは圧縮を実装していないため影響を受けません。圧縮対応が単純に聞こえる一方で実際には複雑である理由の詳細については、gokrazy/rsync issue #35を参照してください。

CVE-2026-43620: 範囲外読み取り後のDOS (6.5)

説明:GitHub Security Advisoryからの引用)

2025年の修正でsend_files()に追加されたparent_ndx<0ガードが、見た目が同一のrecv_files()内のブロックには適用されていませんでした。悪意のあるrsyncサーバーは、互換性フラグでCF_INC_RECURSEを設定し、ソート後の最初のエントリが先頭の「.」ディレクトリではないファイルリスト(これによりrecv_file_list()parent_ndx = -1を設定する)を送信し、さらにndx=0と非ITEM_TRANSFERのiflagワードを持つ転送レコードを送信することで、接続してきた任意のクライアントを確定的なSIGSEGVに陥らせることができます。レシーバーはdir_flist->files[-1]を読み取り、その結果をデリファレンスします。glibc x86-64では、デリファレンスされたポインタは未マップのアドレスに着地するmmapチャンクのメタデータであり、きれいなSEGV_MAPERRとなります。glibc以外のAllocatorについては監査されていません。

到達範囲:攻撃者が制御するURLから通常のプルを行う任意のrsyncクライアント。rsync:// URLとリモートシェル経由のプルの両方で有効です。inc_recurseはプロトコル30以上でのデフォルトであり、被害者側で特別なオプションは必要ありません。

回避策:クライアント側で--no-inc-recursive

アップストリームの修正:

CVE-2026-43620に対するアップストリームの修正では、recv_files()にもparent_ndx<0ガードが追加されています。

gokrazy/rsyncへの影響は?

CVE-2024-12087と同様に、gokrazy/rsyncはこの脆弱性の影響を受けません。gokrazy/rsyncはインクリメンタル再帰モード(--inc-recursive)を実装していないからです。

CVE-2026-43619: さらなるシンボリックリンク競合 (6.3)

説明:GitHub Security Advisoryからの引用)

説明: レシーバーのopen()呼び出しに対するシンボリックリンク競合の以前の修正(CVE-2026-29518)は、他のすべてのパスベースのシステムコールにおける同じ競合クラスを見逃していました:chmod、lchown、utimes、rename、unlink、mkdir、symlink、mknod、link、rmdir、lstatです。「use chroot = no」のrsyncデーモンでは、デーモンホスト上でファイルシステムアクセスを持つローカル攻撃者が、レシーバーのチェックとこれらのシステムコールのいずれかとの間に親ディレクトリコンポーネントにシンボリックリンクを差し込むことで、エクスポートされたモジュール外にリダイレクトできます。修正では、影響を受ける各パスベースのシステムコールを、RESOLVE_BENEATH相当のカーネル強制の隔離下で開かれた親dirfd経由でルーティングします(Linux 5.6以上ではopenat2、FreeBSD 13以上およびmacOS 15以上ではO_RESOLVE_BENEATH、それ以外ではコンポーネントごとのO_NOFOLLOWウォーク)。デフォルトの「use chroot = yes」では影響を受けません。

到達範囲:デーモンホスト上のローカル攻撃者、モジュールパスへの書き込みアクセス、use chroot = noで設定されたデーモン。

アップストリームの修正:

CVE-2026-43619に対するアップストリームの修正では、Goのos.Rootと同じように*at系のシステムコールを使用しています。

Goは防止に役立つか?

はい、Goはこれを防ぐためのAPIを提供しています。多層防御: Goのos.Rootを参照してください。

gokrazy/rsyncへの影響は?

gokrazy/rsyncは影響を受けません。Goのos.Root APIを全面的に使用しているからです。

CVE-2026-43617: ホスト名/ACLバイパス (4.8)

説明:GitHub Security Advisoryからの引用)

グローバルなdaemon chroot = /Xというrsyncd.conf設定で構成されたrsyncデーモンでは、接続してきたクライアントの逆引きDNSルックアップが、デーモンが/Xにchrootした後に実行されていました。/Xにglibcが名前解決に必要とするファイル(/etc/resolv.conf/etc/nsswitch.conf/etc/hosts、NSSサービスモジュール)が含まれていない場合、ルックアップは失敗し、接続元ホスト名は「UNKNOWN」に設定されました。そのため、ホスト名ベースの拒否ルール(「hosts deny = *.evil.example」)はマッチできず、PTRレコードを制御する攻撃者は、管理者が拒否するつもりだったホスト名からでも接続できてしまいました。IPベースのACLは影響を受けません。モジュールごとのuse chroot設定はこの問題とは無関係です。

到達範囲:daemon chroot = /Xで構成されたrsyncデーモン かつ ホスト名ベースのACL かつ /Xにlibcのリゾルバ関連ファイルが含まれていない場合。

アップストリームの修正:

CVE-2026-43617に対するアップストリームの修正では、DNSルックアップをプロトコルのより早い段階に移動しています。

gokrazy/rsyncへの影響は?

gokrazy/rsyncは影響を受けません。ホスト名ベースの許可/拒否リストではなく、IPベースの許可/拒否リストのみを実装しているからです。

CVE-2026-45232: スタックの範囲外書き込み (3.1)

説明:GitHub Security Advisoryからの引用)

rsyncクライアントのHTTP CONNECTプロキシサポートには、establish_proxy_connection()socket.c)における1バイトずれのスタック範囲外書き込みが含まれています。CONNECTリクエストを発行した後、rsyncはプロキシの最初のレスポンス行を1バイトずつ、境界がcp < &buffer[sizeof buffer - 1]である1024バイトのスタックバッファに読み込みます。そのためループはbuffer[0..sizeof-2]にのみ書き込みます。プロキシ(あるいはその前段にいる中間者攻撃者)が、'\n'終端なしで最初のレスポンス行に1023バイト以上を返した場合、ループはcp == &buffer[sizeof buffer - 1]で終了します — ループが決して書き込まなかったスロットなので、*cpは先行するsnprintf()で外向きのCONNECTリクエストをフォーマットした際に残った古いスタック上のバイトを保持しています。ループ後のコードは次を実行します:

if (*cp != '\n')   /* (*cp is uninitialised stack data) */
    cp++;          /* cp now &buffer[sizeof]: one past end */
*cp-- = '\0';      /* one-byte OOB write on the stack */

'\0'はスタック上のbuffer[1024]の終端の1バイト先に着地し、隣接するスタックスロットにあるものを破損させます。AddressSanitizerはestablish_proxy_connectionフレーム内のsocket.c:95stack-buffer-overflowを報告します。

アップストリームの修正:

CVE-2026-45232に対するアップストリームの修正では、攻撃者から提供されたデータの検証を行っています。

gokrazy/rsyncへの影響は?

gokrazy/rsyncはそのようなプロキシサポートを実装していないため、影響を受けません。

Goに関する結論

Goがどうだったかをまとめてみましょう:

  • Goランタイムの境界チェックにより、より深刻なセキュリティ問題がパニックに変わります。
    • パニックは依然としてサービス拒否のリスクですが、はるかに望ましいです。
  • Goはメモリをゼロで初期化するため、CVE-2024-12085のような情報漏洩は起こり得ません。
  • Goのos.Root APIは残りの脆弱性の大半を防ぎます。
  • 12件の脆弱性のうち1件(CVE-2026-43617)のみが、Goを使っても防げなかったアプリケーションロジック自体の正統なバグです。
CVE番号原因リスク (C)Goは役立つか?
2024-12084不十分な検証ヒープバッファオーバーフロー✅ 境界チェック(パニック!)
2024-12085不十分な検証情報漏洩✅ ゼロ初期化
2024-12086検証の欠如任意ファイル漏洩os.Root
2024-12087不十分な検証任意ファイルの書き込みos.Root
2024-12088不十分な検証任意シンボリックリンクの作成os.Root
2024-12747TOCTOU特権ファイルの漏洩os.Root
2026-29518TOCTOU特権ファイルの漏洩os.Root
2026-43617検証の欠如拒否リストが無効❌ ロジックバグ
2026-43618不十分な検証情報漏洩✅ 境界チェック(パニック!)
2026-43619TOCTOU特権ファイルの漏洩os.Root
2026-43620不十分な検証クラッシュ (DOS)✅ 境界チェック(パニック!)
2026-45232検証の欠如メモリ書き込み✅ 境界チェック(パニック!)

gokrazy/rsyncに関する結論

gokrazy/rsyncと公式のアップストリームrsyncのもう一つの大きな違いは、gokrazyの実装が最小限であることです:

  • gokrazy/rsyncは、問題となっている機能自体を実装していないため、多くの脆弱性の影響を受けません。例えば--inc-recursiveなどです。
  • 他のすべてのワイヤプロトコル互換のrsync実装と同様に、gokrazy/rsyncはプロトコルバージョン27を対象としています。なぜなら、それ以降のプロトコルバージョンは大幅な複雑さを伴うからです。
  • 場合によっては、実装することが望ましい機能であっても大きな障壁を伴います。例えば圧縮は一見単純ですが、gokrazy/rsync issue #35に詳細があるように、実際は些細ではありません。

gokrazy/rsyncが各CVEで公開時点で影響を受けていたかどうかを見てみましょう:

CVE番号原因gokrazy/rsyncで実装?gokrazy/rsyncは影響を受けるか?
2024-12084不十分な検証はい⚠️ パニック
2024-12085不十分な検証いいえ(proto 30)✅ 脆弱性なし
2024-12086検証の欠如いいえ(proto 29)✅ 脆弱性なし
2024-12087不十分な検証いいえ(inc-rec✅ 脆弱性なし
2024-12088不十分な検証いいえ(safe-links✅ 脆弱性なし
2024-12747TOCTOUはい❌ 脆弱性あり
2026-29518TOCTOUはい⚠️ 修正済み
2026-43617検証の欠如いいえ(host deny lists)✅ 脆弱性なし
2026-43618不十分な検証いいえ(圧縮)✅ 脆弱性なし
2026-43619TOCTOUはい⚠️ 修正済み
2026-43620不十分な検証いいえ(inc-rec✅ 脆弱性なし
2026-45232検証の欠如いいえ(プロキシ)✅ 脆弱性なし

はっきりさせておきます:既知の脆弱性はすべてgokrazy/rsyncでは修正済みです!上記の表は、各CVEが公開された時点でどのような状態だったかを記録したものです。言い換えれば:

2025年1月の脆弱性が公開されたとき、gokrazy/rsyncはパニックを起こし(CVE-2024-12084)、TOCTOU競合に対して脆弱でした(CVE-2024-12747)。TOCTOU問題の修正過程でCVE-2026-29518を発見し、これはCVEが公開される前にgokrazy/rsyncで修正されました。CVE-2026-43619はさらに後に発見されましたが、同じ修正、すなわちGoのos.Rootを全面的に使うことで、やはりすでにgokrazy/rsyncでは修正済みでした。

不正確な用語

脆弱性レポートを読んでいて、レポートの言葉選びがやや誤解を招くと感じました。ほとんどのレポートは単に「サーバー」と「クライアント」と書いていました。しかし、rsync転送では、rsyncクライアントとrsyncサーバーの両方が、センダー(ファイルをアップロードする側)またはレシーバー(ファイルをダウンロードする側)のいずれの役割も担うことができます!

いくつかのセットアップでは、特定の攻撃を実行困難あるいは不可能にする追加の制約があります。例えば、デーモンモードで動作している場合、ファイルシステムアクセスは事前に設定されたモジュールパスに制限できます(しかしコマンドモードではそうではありません!)。

4つの異なるセットアップと役割/プロトコル階層の概要を示す図を以下に示します:

4つの異なるrsyncセットアップを示す図: (1) rsync --daemon (2) rsync -e ssh; rsync://server/module/dir (3) rsync server:/some/path (4) rsync /src

脆弱性レポートの文脈では、任意ファイル漏洩の脆弱性(CVE-2024-12086)の元のタイトル「Server leaks arbitrary client files(サーバーがクライアントの任意ファイルを漏洩)」は容易に誤解される可能性があると思います。

代わりに、私ならこう言います:rsyncレシーバー悪意のあるセンダーに対して任意のファイルを漏洩する、と。

私は、パッチが当たっていないリモートのrsyncが、例えばSSH経由のコマンドモードで動作している場合に、宛先ツリー外のファイル(例:システムのパスワードデータベースである/etc/shadow)を悪意のあるクライアントセンダーが開かせることができることを検証しました。(ただし、デーモンモードで動作している場合、サーバーは追加のパスのサニタイズを有効にするため、この攻撃は防がれます。)

同様に、シンボリックリンクのパストラバーサル脆弱性(CVE-2024-12087)では「悪意のあるサーバー」と書かれていますが、やはり「悪意のあるセンダー」とすべきであり、これはクライアントでもサーバーでもなり得ます。

OpenBSDのopenrsync(C言語)との比較

OpenBSDプロジェクトはセキュリティ重視で知られていますが、openrsyncはどうでしょうか?

openrsyncは、チェックサム長を検証し、1つのチェックサムサイズ/アルゴリズム(MD4)のみをサポートしているため、ヒープバッファオーバーフロー(CVE-2024-12084)およびスタック情報漏洩(CVE-2024-12085)の脆弱性の影響を受けません。

openrsyncは、関連する機能を実装していないため、CVE-2024-12086、CVE-2024-12087およびCVE-2024-12088の影響を受けません(gokrazy/rsyncと同様です)。仮に脆弱であったとしても、openrsyncの多層防御策であるOpenBSDのunveil(2)pledge(2)によるファイルシステムアクセス制限が、少なくともOpenBSD上で動作している場合には攻撃の成功を防いでいたでしょう。

openrsyncは、シンボリックリンク対応を実装した当初から一貫してO_NOFOLLOWを使用してきたため、CVE-2024-12747の影響を受けません。しかし、O_NOFOLLOWはこの問題に対する十分な修正ではないため、openrsyncはCVE-2026-29518の影響を受けます

上記は2025年1月のバッチの脆弱性についてですが、2026年5月のバッチについても同様で、ほとんどの機能が単に実装されていないだけです。

総じて、私はこう言いたいです:よくやった、Kristapsとコントリビューターの皆さん!検証を diligently に実装し、攻撃対象領域を制限し、多層防御策を講じることで、openrsyncは報告された脆弱性のほぼすべてについて影響を受けずに済んでいます。

多層防御

Linux上で多層防御のためにどのようなAPIや環境を利用できるでしょうか?

gokrazy/rsyncが対応しているものを、伝統的なものからモダンなものへと順に紹介します。

Linuxのマウント名前空間

gokrazy/rsyncプロジェクトを開始して数週間以内に、Linuxで権限を落とし、マウント/PID名前空間を使ってrsyncサーバーが扱えるファイルシステムオブジェクトを制限する対応を追加しました。

このアプローチはパストラバーサル攻撃の緩和に非常によく機能しますが、権限を必要とします。つまりrootとして実行するか、Linuxのユーザー名前空間内で実行する必要があります(ディストリビューション/システムで有効になっている場合)。

この制約により、マウント名前空間はサーバー用途には非常に適していますが、通常は人間のユーザーアカウントで実行される対話的な一回限りの転送では通常利用できません。

systemdによる強化

Linuxのマウント/PID名前空間対応を導入したのと同じコミットで、ホームディレクトリへのファイルシステムアクセスを制限するsystemdサービスファイルも同梱し、READMEでもユースケースに応じてファイルシステムアクセスをさらに制限することを推奨しました。

これらのファイルシステム制限は、正しく設定されていれば、ファイル漏洩(CVE-2024-12086)やパストラバーサル(CVE-2024-12087)の脆弱性を緩和します。

シンボリックリンクの競合状態(CVE-2024-12747)はrsyncプロセスを介した権限昇格に依存しますが、DynamicUser機能のおかげで、私たちのプロセスは他のユーザーよりも少ない権限で動作します。

マウント名前空間と同様に、これらの対策はサーバー用途には素晴らしいですが、対話的な一回限りの用途には設定が煩雑すぎます。

Linux Landlock

Justine氏のブログ投稿Porting OpenBSD pledge() to Linux (2022)を読んで、LinuxがLandlock APIという、権限なしでプロセスごとのアクセス制御を行うためのAPIを提供していることを思い出しました。これはopenrsyncが使っているOpenBSDのunveil(2)システムコールに似ています。基本的な考え方は、プログラムが作業するディレクトリを把握した時点で、unveil("/home/michael/backups", "rw");のような呼び出しを行い、他のファイルシステムの場所へのアクセスを失うというものです。

以前GoのミートアップでLandlockについて聞いていたので、GoでのLandlock対応があることは知っていました。2022年にはすでにgokrazyのカーネルイメージでLandlock対応を有効にしていました。

そこで2025年3月に試してみて、ファイルシステムアクセスを制限するためにLandlock対応を実装しました。数時間かかりましたが、これは一見想定するより少し長く感じられるかもしれません。テスト環境でLandlockを動作させる(あるいはスキップする)ことがいくつかの障壁にぶつかりました:テストでは同じプロセス内で実行される多くの関数を定義していましたが、ルールセットを繰り返し追加すると、プロセスあたり16(!)というポリシーレイヤーの上限を超えてしまったのです。

一度うまく設定できてしまえば、素晴らしい解決策です。今では、特権のないgokrazy/rsyncの呼び出しであっても、rsync転送をソース(読み取り専用)や宛先ディレクトリ(読み書き可能)に制限できます!🎉


Landlockの欠点は、プロセスレベルで動作することです。つまりLandlockポリシーにはプログラムが必要とするファイルを含める必要があります。例えばgokrazy/rsyncはユーザーIDの解決のために/etc/passwdを読み取れる必要があるため、攻撃者が/etc/passwdファイルを狙っている場合、Landlockは役に立ちません。

Goのos.Root

2025年2月のGo 1.24リリースでは、os.Root APIが導入されました。これはパストラバーサル耐性があり、The Go Blog: Traversal-resistant file APIs(Damien Neil氏、2025年3月)を参照してください。このAPIはLandlockと比較してより細かい粒度(ファイルシステム操作ごと)での制御を可能にします。

Go 1.25(2025年8月リリース)ではos.Rootにさらに多くのメソッドが追加され、ほとんどのファイルシステム用途で便利な選択肢となりました。

gokrazy/rsyncのファイルシステム利用のすべてをos.Rootを使うように変換しました。これはぴったりです。ユーザーは入力/出力ディレクトリを設定しますが、ネットワーク経由で受信したファイル名は信頼できません。まさにos.Rootが設計された用途です!


os.Rootの利用を検討し始めた当初は、例えばmknod(2)でデバイスノードファイルを作成するような、一部のシステムコールはこのAPIでは本質的に実行できないと思っていました。Damien氏の説明はこうでした:

mknodはサポートされません。

ただし、安全なmknodを有効にするためにそれを利用することは可能です:

実際にどうなるか気になる方は、gokrazy/rsyncでの使用例であるinternal/receiver/generatormknod_linux.goの15〜29行目をご覧ください。

もう一つのつまずきは、mknodat(2)とは異なり、Linuxはbind(2)のみを実装しており、bindatは存在しない(Linux 7.0時点)ことに気づいたことでした!

幸い、Lennart Poettering氏が指摘してくれたように、bindatなしでパス解決をスキップするトリックがあります:

当面は/proc/self/<fd>/foobarにbindすればおそらく大丈夫です…

そして実際、これは機能します!既知の安全な/proc/self/<fd>の後に、パスではなくベース名(パスの最後のコンポーネント)のみを指定するため、パス解決がスキップされます(49〜56行目を参照)。

この2つのヒントのおかげで、gokrazy/rsync v0.3.1以降では全面的にos.Rootを使用しており、つまりすべてのファイルシステムアクセスがトラバーサルセーフになっています!🥳

結論

検証の欠如が脆弱性を生む

TOCTOU脆弱性(CVE-2024-12747、CVE-2026-29518およびCVE-2026-43619)を除けば、他のすべての脆弱性は入力検証の欠如または不正確さが原因だったことは興味深い点です。3つのケースでは、そもそも検証がまったく存在しませんでした。別のケース(CVE-2024-12088)では、ファイルシステムのパス解決という主題が十分にトリッキーで、既存の検証がすべてのエッジケースをカバーしていませんでした。

Goに関する結論セクションでより詳しく説明されているように、最も価値のある構造的な修正は、境界チェック(=常時行われる検証)とGoのos.Rootのような安全がデフォルトのAPIを提供することです。

複雑すぎること

いくつかの脆弱性はrsyncプロトコルの進化から生じました:コードはかつては十分な検証を正しく行っていましたが、その後新しい機能が追加されたのです。例えば、チェックサムアルゴリズムのネゴシエーションが追加されたとき(プロトコルバージョン30)、検証は正しく更新されませんでした。インクリメンタル再帰が追加されたとき(これもプロトコルバージョン30)、個々のファイルリストには意味があった検証が、インクリメンタルなファイルリストをマージするという新しい処理アプローチ向けに更新されませんでした。

複雑さを避けることで脆弱性を避けられます!gokrazy/rsyncもopenrsyncも、脆弱性のある機能自体を実装していないというだけで、12件中8件のセキュリティ脆弱性の影響を受けずに済んでいます。

もちろん、これらの機能はある時点で誰かにとって価値があったからこそrsyncに追加されたのであり、もちろんソフトウェア開発を今後一切やめるべきだと言っているわけではありません。

しかし、ユースケースの複雑さに見合った、比例する複雑さの実装を使うことが理想的だと考えています。言い換えれば、単純なユースケースには単純な実装を選び、完全に機能を備えた実装は必要な場合にのみ選ぶべきです。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント