[インデックス 16294] ファイルの概要
このコミットは、Go言語の標準ライブラリ archive/tar パッケージにおける tar アーカイブの読み込み処理に関するバグ修正です。具体的には、tar ヘッダー内の数値フィールド(例えばUIDやGIDなど)がヌル文字(NUL)で埋められている場合に、それらのフィールドを正しくパースできない問題を解決します。これにより、一部の不正な、または非標準的な tar アーカイブでもGoの archive/tar パッケージで適切に処理できるようになります。
コミット
- コミットハッシュ:
9c945809219d88148a2c83f04f69b88dc86ebc0d - 作者: Shenghou Ma minux.ma@gmail.com
- コミット日時: 2013年5月15日 水曜日 04:40:42 +0800
- 変更ファイル:
src/pkg/archive/tar/reader.gosrc/pkg/archive/tar/reader_test.gosrc/pkg/archive/tar/testdata/nil-uid.tar(新規追加のバイナリテストデータ)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9c945809219d88148a2c83f04f69b88dc86ebc0d
元コミット内容
archive/tar: skip NUL-filled unused octal fields
Fixes #5290.
R=golang-dev, dave, bradfitz, r
CC=golang-dev
https://golang.org/cl/8763044
変更の背景
このコミットは、Go言語のIssue 5290 (golang.org/issue/5290) で報告されたバグを修正するために行われました。このバグは、tar アーカイブのヘッダー内で、本来数値が格納されるべきフィールド(例えばユーザーID Uid やグループID Gid)が、未使用の場合にヌル文字(\x00)で埋められていると、archive/tar パッケージがそのフィールドを正しくパースできず、エラーとなるというものでした。
tar フォーマットの仕様では、数値フィールドは通常、ASCIIの八進数表現で格納され、残りのスペースはスペース文字で埋められるか、ヌル文字で埋められることがあります。しかし、一部の tar 実装では、未使用のフィールドを完全にヌル文字で埋めることがあり、Goの archive/tar パッケージの既存の octal 関数は、このようなヌル文字で埋められたフィールドを適切に処理できませんでした。具体的には、octal 関数は先頭のスペースをスキップするロジックを持っていましたが、先頭のヌル文字をスキップするロジックが不足していました。これにより、ヌル文字が残った状態で strconv.ParseUint が呼び出され、パースエラーが発生していました。
この問題は、特に Uid や Gid が 0 の場合に顕著に現れました。0 は tar ヘッダーでは 0000000 のように八進数で表現されますが、これがヌル文字で埋められると、Goのパーサーが誤動作し、tar アーカイブの読み込みに失敗していました。この修正は、より堅牢な tar アーカイブのパースを実現し、異なる tar 実装によって生成されたアーカイブとの互換性を向上させることを目的としています。
前提知識の解説
TARアーカイブフォーマット
TAR (Tape ARchive) は、複数のファイルを一つのアーカイブファイルにまとめるためのファイルフォーマットです。主にUnix系システムでファイルのバックアップや配布に利用されます。TARアーカイブは、一連の「ヘッダーブロック」とそれに続く「データブロック」で構成されます。各ファイルやディレクトリは、そのメタデータ(ファイル名、パーミッション、所有者、サイズ、タイムスタンプなど)を記述するヘッダーブロックと、実際のファイル内容を含むデータブロックからなります。
TARヘッダーの数値フィールドと八進数表現
TARヘッダーには、ファイルのパーミッション(mode)、ユーザーID(uid)、グループID(gid)、ファイルサイズ(size)、最終更新時刻(mtime)など、多くの数値情報が格納されます。これらの数値フィールドは、通常、ASCII文字で表現された八進数(オクタル)文字列として格納されます。例えば、パーミッション 0644 は 0000644 のように表現されます。
フィールドのパディングとヌル文字(NUL)
TARヘッダーの各フィールドは固定長です。例えば、uid や gid フィールドは8バイト長です。数値がフィールド長に満たない場合、残りのスペースはパディングされます。一般的なパディング文字はスペース( )ですが、一部の tar 実装ではヌル文字(\x00)でパディングされることがあります。ヌル文字はASCIIコードで0x00であり、文字列の終端を示すためにも使われる文字です。
Go言語の archive/tar パッケージ
Go言語の標準ライブラリ archive/tar パッケージは、TARアーカイブの作成と読み込みをサポートします。このパッケージは、tar.Reader と tar.Writer という主要な型を提供し、それぞれTARアーカイブからの読み込みとTARアーカイブへの書き込みを行います。内部的には、TARヘッダーの各フィールドをパースするためのヘルパー関数が多数存在します。このコミットで修正される octal 関数もその一つで、八進数文字列を int64 に変換する役割を担っています。
strconv.ParseUint
Go言語の strconv パッケージは、文字列と数値の変換を提供します。strconv.ParseUint 関数は、指定された基数(この場合は8進数なので8)で符号なし整数をパースします。この関数は、パース対象の文字列が有効な数値表現でない場合にエラーを返します。
技術的詳細
このコミットの核心は、src/pkg/archive/tar/reader.go 内の octal 関数の変更です。この関数は、TARヘッダーから読み取ったバイトスライスを八進数としてパースし、int64 型の数値に変換する役割を担っています。
変更前の octal 関数は、以下のロジックでバイトスライスを処理していました。
- 先頭のスペースの除去:
for len(b) > 0 && b[0] == ' ' { b = b[1:] }- これは、フィールドの先頭に存在する可能性のあるスペース文字をスキップするためのものでした。
- 末尾のヌル文字とスペースの除去:
for len(b) > 0 && (b[len(b)-1] == ' ' || b[len(b)-1] == '\x00') { b = b[0 : len(b)-1] }- これは、フィールドの末尾に存在する可能性のあるスペース文字やヌル文字をスキップするためのものでした。
この既存のロジックの問題点は、先頭のヌル文字をスキップする処理がなかったことです。tar ヘッダーの数値フィールドが、例えば \x00\x00\x0000000 のように先頭がヌル文字で埋められている場合、上記のロジックでは先頭のヌル文字が残ったまま strconv.ParseUint に渡されてしまいます。strconv.ParseUint はヌル文字を有効な八進数の一部として認識しないため、パースエラーが発生していました。
新しい octal 関数では、この問題を解決するために bytes.Trim 関数が導入されました。
b = bytes.Trim(b, " \x00")
この一行の変更は、以下の効果をもたらします。
bytes.Trim(s, cutset)は、バイトスライスsの先頭と末尾から、cutsetで指定された文字(この場合はスペースとヌル文字\x00)をすべて削除します。- これにより、先頭と末尾の両方からスペースとヌル文字が確実に除去されるため、
strconv.ParseUintに渡される文字列は純粋な八進数表現のみとなります。
さらに、bytes.Trim の結果、バイトスライス b が空になった場合のハンドリングも追加されました。
if len(b) == 0 {
return 0
}
これは、フィールドが完全にスペースやヌル文字で埋められていた場合(例えば、Uid や Gid が 0 で、フィールド全体がヌル文字で埋められているようなケース)に、0 を返すようにするためのものです。これにより、空の文字列を strconv.ParseUint に渡すことによるエラーも回避されます。
この変更により、archive/tar パッケージは、ヌル文字で埋められた tar ヘッダーの数値フィールドをより堅牢に処理できるようになり、tar アーカイブの互換性が向上しました。
コアとなるコードの変更箇所
src/pkg/archive/tar/reader.go の octal 関数が変更されました。
--- a/src/pkg/archive/tar/reader.go
+++ b/src/pkg/archive/tar/reader.go
@@ -243,13 +243,15 @@ func (tr *Reader) octal(b []byte) int64 {
return x
}
- // Removing leading spaces.
- for len(b) > 0 && b[0] == ' ' {
- b = b[1:]
- }
- // Removing trailing NULs and spaces.
- for len(b) > 0 && (b[len(b)-1] == ' ' || b[len(b)-1] == '\x00') {
- b = b[0 : len(b)-1]
+ // Because unused fields are filled with NULs, we need
+ // to skip leading NULs. Fields may also be padded with
+ // spaces or NULs.
+ // So we remove leading and trailing NULs and spaces to
+ // be sure.
+ b = bytes.Trim(b, " \x00")
+
+ if len(b) == 0 {
+ return 0
}
x, err := strconv.ParseUint(cString(b), 8, 64)
if err != nil {
また、この変更を検証するためのテストケースが src/pkg/archive/tar/reader_test.go に追加され、src/pkg/archive/tar/testdata/nil-uid.tar という新しいテスト用の tar ファイルが追加されました。この nil-uid.tar は、Uid フィールドがヌル文字で埋められている特定のケースを再現するためのバイナリファイルです。
コアとなるコードの解説
変更の核心は、octal 関数内の以下の行です。
b = bytes.Trim(b, " \x00")
この行は、入力バイトスライス b の先頭と末尾から、スペース文字 ( ) とヌル文字 (\x00) をすべて取り除きます。これにより、tar ヘッダーの数値フィールドがスペースやヌル文字でパディングされている場合でも、純粋な八進数文字列のみが残るようになります。
変更前のコードでは、先頭のスペースと末尾のスペース・ヌル文字を個別にループで削除していましたが、先頭のヌル文字の削除が考慮されていませんでした。bytes.Trim を使用することで、この処理がより簡潔かつ正確に行われるようになりました。
その後の if len(b) == 0 { return 0 } の追加は、bytes.Trim の結果、バイトスライスが空になった場合の安全策です。これは、例えば Uid や Gid が 0 で、フィールド全体がヌル文字で埋められているような場合に発生し得ます。この場合、0 を返すことで、strconv.ParseUint に空の文字列が渡されてエラーになるのを防ぎます。
この修正により、archive/tar パッケージは、より多様な tar アーカイブ(特に、非標準的なパディングを持つもの)を正しく処理できるようになり、堅牢性が向上しました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/9c945809219d88148a2c83f04f69b88dc86ebc0d
- Go Issue 5290: https://golang.org/issue/5290
- Gerrit Change 8763044: https://golang.org/cl/8763044
参考にした情報源リンク
- Go Issue 5290の議論内容
bytes.Trim関数のGoドキュメント- TARファイルフォーマットの仕様に関する一般的な情報 (例: POSIX.1-1988 TAR format)
strconv.ParseUint関数のGoドキュメント