🐛

rsyncでチェックサムによる転送ファイル差分計算時に特定サイズのファイルで存在しない差分を誤検知する問題

に公開

概要

rsyncはファイルやディレクトリを効率的に同期・転送するためのコマンドです。

rsyncは転送対象のファイルを特定するために差分をチェックします。デフォルトではタイムスタンプとファイルサイズで差分を判定しますが、--checksumオプションを指定するとファイル内容のチェックサム(ハッシュ値)で判定します。

このチェックサム計算にバグがあり、特定条件下で同一ファイルなのに差分ありと誤認識される問題があります。

このバグはプロトコル29かつチェックサムによる差分計算を使用している場合に発生します。
デフォルトではタイムスタンプで差分計算するため、多くの場合は影響を受けません。

macOS 15.7 (Sequoia) にプリインストールされているrsync (openrsync)はプロトコル29を使用しています。

$ rsync -V
openrsync: protocol version 29
rsync version 2.6.9 compatible

発生条件

  • ローカル→リモート間の転送(ローカル間では発生しない)
  • プロトコル29を使用
  • --checksumオプションで差分計算
  • サイズが64の倍数バイトのファイル
  • リモート側がrsync 3.2.5(3.1.3では発生しない)

正確な条件が不明なため、他の条件も影響している可能性があります。

再現手順

your-remote-hostを適切なリモートホスト名に置き換えてください。

# 64バイトのテストファイルを作成
head -c 64 /dev/urandom | base64 | head -c 64 > /tmp/test64.bin

# リモートに同期
rsync -avz /tmp/test64.bin your-remote-host:~/test64.bin

# チェックサムで差分確認(バグが発生する)
rsync -avz --checksum --protocol=29 --dry-run --itemize-changes  /tmp/test64.bin your-remote-host:~/test64.bin

オプション説明

オプション 説明
--checksum 差分計算にチェックサムを使用
--protocol=29 プロトコル29を強制
--dry-run 実際には転送しない
--itemize-changes 変更内容を詳細表示

なお、--protocol=29はGPL版rsyncでopenrsyncとプロトコルを合わせるために使用しています。
openrsyncでは指定しなければプロトコル29が使用されます。

出力例

building file list ... done
<fc......... test64.bin

sent 92 bytes  received 30 bytes  81.33 bytes/sec
total size is 64  speedup is 0.52 (DRY RUN)

<fcはチェックサム不一致を意味しており、同一ファイルを比較しているにもかかわらず、違うファイルとして認識されていることが分かります。
差分がなければこの行は表示されません。

回避法

  • プロトコル30以降を使用する
  • GPL版rsyncで--checksum-choice=md4またはmd5を指定
    • openrsyncでは--checksum-choiceオプションがないため使用不可
  • タイムスタンプによる差分計算を使用(デフォルト)
  • リモートでGPL版rsync 3.2以外を使用する(要検証)

検証環境

openrsync固有の問題ではないことを確認するため、HomebrewでGPL版rsyncをインストールして検証しました。
Homebrew版をインストールするとrsyncコマンドがHomebrew版に置き換わるため、macOSプリインストール版は/usr/bin/rsync、Homebrew版は/opt/homebrew/bin/rsyncとフルパスで指定して使い分ける必要があります。

環境 OS rsyncバージョン
ローカル (macOSプリインストール) macOS 15.7.2 openrsync protocol version 29
ローカル (Homebrew) 同上 GPL版rsync 3.4.1 protocol version 32
リモート (発生) Rocky Linux 9.7 GPL版rsync 3.2.5 protocol version 31
リモート (発生せず) RHEL 8.10 GPL版rsync 3.1.3 protocol version 31
GitHubで編集を提案

Discussion