Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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 が呼び出され、パースエラーが発生していました。

この問題は、特に UidGid0 の場合に顕著に現れました。0tar ヘッダーでは 0000000 のように八進数で表現されますが、これがヌル文字で埋められると、Goのパーサーが誤動作し、tar アーカイブの読み込みに失敗していました。この修正は、より堅牢な tar アーカイブのパースを実現し、異なる tar 実装によって生成されたアーカイブとの互換性を向上させることを目的としています。

前提知識の解説

TARアーカイブフォーマット

TAR (Tape ARchive) は、複数のファイルを一つのアーカイブファイルにまとめるためのファイルフォーマットです。主にUnix系システムでファイルのバックアップや配布に利用されます。TARアーカイブは、一連の「ヘッダーブロック」とそれに続く「データブロック」で構成されます。各ファイルやディレクトリは、そのメタデータ(ファイル名、パーミッション、所有者、サイズ、タイムスタンプなど)を記述するヘッダーブロックと、実際のファイル内容を含むデータブロックからなります。

TARヘッダーの数値フィールドと八進数表現

TARヘッダーには、ファイルのパーミッション(mode)、ユーザーID(uid)、グループID(gid)、ファイルサイズ(size)、最終更新時刻(mtime)など、多くの数値情報が格納されます。これらの数値フィールドは、通常、ASCII文字で表現された八進数(オクタル)文字列として格納されます。例えば、パーミッション 06440000644 のように表現されます。

フィールドのパディングとヌル文字(NUL)

TARヘッダーの各フィールドは固定長です。例えば、uidgid フィールドは8バイト長です。数値がフィールド長に満たない場合、残りのスペースはパディングされます。一般的なパディング文字はスペース( )ですが、一部の tar 実装ではヌル文字(\x00)でパディングされることがあります。ヌル文字はASCIIコードで0x00であり、文字列の終端を示すためにも使われる文字です。

Go言語の archive/tar パッケージ

Go言語の標準ライブラリ archive/tar パッケージは、TARアーカイブの作成と読み込みをサポートします。このパッケージは、tar.Readertar.Writer という主要な型を提供し、それぞれTARアーカイブからの読み込みとTARアーカイブへの書き込みを行います。内部的には、TARヘッダーの各フィールドをパースするためのヘルパー関数が多数存在します。このコミットで修正される octal 関数もその一つで、八進数文字列を int64 に変換する役割を担っています。

strconv.ParseUint

Go言語の strconv パッケージは、文字列と数値の変換を提供します。strconv.ParseUint 関数は、指定された基数(この場合は8進数なので8)で符号なし整数をパースします。この関数は、パース対象の文字列が有効な数値表現でない場合にエラーを返します。

技術的詳細

このコミットの核心は、src/pkg/archive/tar/reader.go 内の octal 関数の変更です。この関数は、TARヘッダーから読み取ったバイトスライスを八進数としてパースし、int64 型の数値に変換する役割を担っています。

変更前の octal 関数は、以下のロジックでバイトスライスを処理していました。

  1. 先頭のスペースの除去: for len(b) > 0 && b[0] == ' ' { b = b[1:] }
    • これは、フィールドの先頭に存在する可能性のあるスペース文字をスキップするためのものでした。
  2. 末尾のヌル文字とスペースの除去: 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
}

これは、フィールドが完全にスペースやヌル文字で埋められていた場合(例えば、UidGid0 で、フィールド全体がヌル文字で埋められているようなケース)に、0 を返すようにするためのものです。これにより、空の文字列を strconv.ParseUint に渡すことによるエラーも回避されます。

この変更により、archive/tar パッケージは、ヌル文字で埋められた tar ヘッダーの数値フィールドをより堅牢に処理できるようになり、tar アーカイブの互換性が向上しました。

コアとなるコードの変更箇所

src/pkg/archive/tar/reader.gooctal 関数が変更されました。

--- 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 の結果、バイトスライスが空になった場合の安全策です。これは、例えば UidGid0 で、フィールド全体がヌル文字で埋められているような場合に発生し得ます。この場合、0 を返すことで、strconv.ParseUint に空の文字列が渡されてエラーになるのを防ぎます。

この修正により、archive/tar パッケージは、より多様な tar アーカイブ(特に、非標準的なパディングを持つもの)を正しく処理できるようになり、堅牢性が向上しました。

関連リンク

参考にした情報源リンク

  • Go Issue 5290の議論内容
  • bytes.Trim 関数のGoドキュメント
  • TARファイルフォーマットの仕様に関する一般的な情報 (例: POSIX.1-1988 TAR format)
  • strconv.ParseUint 関数のGoドキュメント