[インデックス 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.go
src/pkg/archive/tar/reader_test.go
src/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ドキュメント