[インデックス 17493] ファイルの概要
このコミットは、Go言語の archive/tar
パッケージにおける USTAR
形式の長期ファイル名処理に関するバグ修正とテストの追加を扱っています。具体的には、特定の長いファイル名が USTAR
形式で正しく分割されないケースを修正し、それに伴うエラーを解消することを目的としています。
コミット
commit 4e998d71c9abf1a2d302307301688108ccfffc56
Author: Marco Hennings <marco.hennings@freiheit.com>
Date: Fri Sep 6 16:49:38 2013 -0400
archive/tar: fix a case where USTAR-split is not working correctly.
For some long filenames the USTAR-split code does not work
correctly. It is wrongly assumed that the path would not be too long,
but it is.
The user visible result was that a filename was split, but it still
caused an error.
The cause was a wrongly calculated nlen. In addition I noticed that
at this place it is also seems necessary to check if the prefix will
fit in the 155 chars available for the prefix.
R=dsymonds, rsc
CC=golang-dev
https://golang.org/cl/13300046
---
src/pkg/archive/tar/writer.go | 7 +++++--
src/pkg/archive/tar/writer_test.go | 36 ++++++++++++++++++++++++++++++++++++\n 2 files changed, 41 insertions(+), 2 deletions(-)
diff --git a/src/pkg/archive/tar/writer.go b/src/pkg/archive/tar/writer.go
index c0325194a2..549f1464c3 100644
--- a/src/pkg/archive/tar/writer.go
+++ b/src/pkg/archive/tar/writer.go
@@ -263,8 +263,11 @@ func (tw *Writer) splitUSTARLongName(name string) (prefix, suffix string, err er
length--
}
i := strings.LastIndex(name[:length], "/")
- nlen := length - i - 1
- if i <= 0 || nlen > fileNameSize || nlen == 0 {
+ // nlen contains the resulting length in the name field.
+ // plen contains the resulting length in the prefix field.
+ nlen := len(name) - i - 1
+ plen := i
+ if i <= 0 || nlen > fileNameSize || nlen == 0 || plen > fileNamePrefixSize {
err = errNameTooLong
return
}
diff --git a/src/pkg/archive/tar/writer_test.go b/src/pkg/archive/tar/writer_test.go
index cddcbbc254..30ebf977ac 100644
--- a/src/pkg/archive/tar/writer_test.go
+++ b/src/pkg/archive/tar/writer_test.go
@@ -355,3 +355,39 @@ func TestPAXHeader(t *testing.T) {
}
}
}
+
+func TestUSTARLongName(t *testing.T) {
+ // Create an archive with a path that failed to split with USTAR extension in previous versions.
+ fileinfo, err := os.Stat("testdata/small.txt")
+ if err != nil {
+ t.Fatal(err)
+ }
+ hdr, err := FileInfoHeader(fileinfo, "")
+ hdr.Typeflag = TypeDir
+ if err != nil {
+ t.Fatalf("os.Stat:1 %v", err)
+ }
+ // Force a PAX long name to be written. The name was taken from a practical example
+ // that fails and replaced ever char through numbers to anonymize the sample.
+ longName := "/0000_0000000/00000-000000000/0000_0000000/00000-0000000000000/0000_0000000/00000-0000000-00000000/0000_0000000/00000000/0000_0000000/000/0000_0000000/00000000v00/0000_0000000/000000/0000_0000000/0000000/0000_0000000/00000y-00/0000/0000/00000000/0x000000/"
+ hdr.Name = longName
+
+ hdr.Size = 0
+ var buf bytes.Buffer
+ writer := NewWriter(&buf)
+ if err := writer.WriteHeader(hdr); err != nil {
+ t.Fatal(err)
+ }
+ if err := writer.Close(); err != nil {
+ t.Fatal(err)
+ }
+ // Test that we can get a long name back out of the archive.
+ reader := NewReader(&buf)
+ hdr, err = reader.Next()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if hdr.Name != longName {
+ t.Fatal("Couldn't recover long name")
+ }
+}
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4e998d71c9abf1a2d302307301688108ccfffc56
元コミット内容
archive/tar: fix a case where USTAR-split is not working correctly.
For some long filenames the USTAR-split code does not work correctly. It is wrongly assumed that the path would not be too long, but it is.
The user visible result was that a filename was split, but it still caused an error.
The cause was a wrongly calculated nlen. In addition I noticed that at this place it is also seems necessary to check if the prefix will fit in the 155 chars available for the prefix.
R=dsymonds, rsc
CC=golang-dev
https://golang.org/cl/13300046
変更の背景
このコミットは、archive/tar
パッケージが USTAR
形式で非常に長いファイル名を処理する際に発生していたバグを修正するために導入されました。既存の実装では、ファイル名が長すぎる場合に USTAR
形式の仕様に従って name
フィールドと prefix
フィールドに分割するロジックに誤りがありました。
具体的には、name
フィールドの長さ (nlen
) の計算が間違っていたため、分割されたファイル名が USTAR
の name
フィールドの最大長 (100文字) を超えてしまうことがありました。これにより、ファイル名が分割されたにもかかわらず、依然としてエラーが発生し、アーカイブの作成や読み取りが失敗するという問題がユーザーに影響を与えていました。
さらに、prefix
フィールドが利用可能な155文字のスペースに収まるかどうかのチェックが不足していたことも問題の一因でした。このコミットは、これらの計算ミスとチェックの不足を修正し、USTAR
形式の長期ファイル名処理の堅牢性を向上させることを目的としています。
前提知識の解説
Tarアーカイブ形式
Tar (Tape ARchive) は、複数のファイルを一つのアーカイブファイルにまとめるためのファイル形式です。主にUnix系システムで利用され、ファイルのバックアップや配布によく用いられます。Tarアーカイブは、ファイルデータと、ファイル名、パーミッション、タイムスタンプなどのメタデータを含むヘッダーブロックで構成されます。
USTAR (POSIX 1003.1) 形式
初期のTar形式(V7 Tar)では、ファイル名の長さに100文字という制限がありました。この制限を克服するために、POSIX 1003.1標準で USTAR
(Unix Standard TAR) 形式が導入されました。
USTAR
形式の主な特徴は、長いファイル名を扱うための prefix
フィールドの導入です。
name
フィールド: ファイル名の末尾100文字を格納します。prefix
フィールド: ファイル名の先頭部分を格納します。このフィールドの最大長は155文字です。
長いファイル名を USTAR
形式でアーカイブする場合、パスはディレクトリセパレータ(/
)で分割され、name
フィールドと prefix
フィールドにそれぞれ格納されます。これにより、最大で256文字(prefix
155文字 + /
1文字 + name
100文字)までのファイル名を扱うことが可能になります。
重要なのは、パスがディレクトリセパレータで分割可能である必要がある点です。もし分割できない場合、USTAR
形式ではアーカイブできません。また、name
および prefix
フィールドは通常ヌル終端されますが、フィールド全体が名前の格納に使用される場合はヌル終端されないこともあります。一部の実装ではヌル終端を強制するため、実質的な長さが1バイト短くなることがあります(prefix
は154文字、name
は99文字)。
Go言語の archive/tar
パッケージ
Go言語の標準ライブラリには、Tarアーカイブの作成と読み取りをサポートする archive/tar
パッケージが含まれています。このパッケージは、Tarアーカイブの様々な形式(V7、USTAR、PAXなど)を扱うことができます。
Writer
構造体はTarアーカイブへの書き込みを担当し、WriteHeader
メソッドを通じて各ファイルのヘッダー情報を書き込みます。この際、ファイル名が長い場合には splitUSTARLongName
のような内部関数が呼び出され、USTAR
形式の仕様に従ってファイル名を分割する処理が行われます。
技術的詳細
このコミットの技術的な核心は、archive/tar
パッケージ内の splitUSTARLongName
関数における nlen
(name length) と plen
(prefix length) の計算ロジックの修正にあります。
元のコードでは、nlen
の計算が length - i - 1
となっていました。ここで length
はファイル名の全長、i
は最後のスラッシュのインデックスです。この計算は、name
フィールドに格納されるべき部分の長さを誤って評価していました。
修正後のコードでは、nlen
は len(name) - i - 1
となり、これはファイル名の全長から最後のスラッシュまでの部分(スラッシュ自体も含む)を引くことで、name
フィールドに格納されるべきサフィックス部分の正確な長さを計算しています。
さらに重要な変更点として、prefix
フィールドの長さ (plen
) のチェックが追加されました。plen
は i
、つまり最後のスラッシュのインデックスとして定義されています。これは、ファイル名の先頭から最後のスラッシュまでの部分が prefix
フィールドに格納されるため、その長さが prefix
フィールドの最大長 (fileNamePrefixSize
、USTARでは155文字) を超えていないかを確認する必要があるためです。
修正前のコードでは、if i <= 0 || nlen > fileNameSize || nlen == 0
という条件のみでエラーチェックを行っていました。これは name
フィールドの長さと、スラッシュの位置に関する基本的なチェックでしたが、prefix
フィールドの長さに関する明示的なチェックが欠けていました。
修正後の条件 if i <= 0 || nlen > fileNameSize || nlen == 0 || plen > fileNamePrefixSize
は、plen > fileNamePrefixSize
という新しい条件を追加することで、prefix
フィールドが USTAR
の仕様で許容される最大長を超えていないかを厳密にチェックするようになりました。これにより、USTAR
形式の制約をより正確に遵守し、不正なファイル名分割によるエラーを防ぐことができます。
テストケース TestUSTARLongName
は、この修正が正しく機能することを確認するために追加されました。このテストは、以前のバージョンで分割に失敗した特定の長いファイル名 (longName
) を使用して、Writer
が正しくアーカイブを作成し、Reader
がそのアーカイブから元の長いファイル名を正確に復元できることを検証します。これは、バグが実際に修正され、リグレッションが発生しないことを保証するための重要なステップです。
コアとなるコードの変更箇所
src/pkg/archive/tar/writer.go
--- a/src/pkg/archive/tar/writer.go
+++ b/src/pkg/archive/tar/writer.go
@@ -263,8 +263,11 @@ func (tw *Writer) splitUSTARLongName(name string) (prefix, suffix string, err er
length--
}
i := strings.LastIndex(name[:length], "/")
- nlen := length - i - 1
- if i <= 0 || nlen > fileNameSize || nlen == 0 {
+ // nlen contains the resulting length in the name field.
+ // plen contains the resulting length in the prefix field.
+ nlen := len(name) - i - 1
+ plen := i
+ if i <= 0 || nlen > fileNameSize || nlen == 0 || plen > fileNamePrefixSize {
err = errNameTooLong
return
}
src/pkg/archive/tar/writer_test.go
--- a/src/pkg/archive/tar/writer_test.go
+++ b/src/pkg/archive/tar/writer_test.go
@@ -355,3 +355,39 @@ func TestPAXHeader(t *testing.T) {
}
}
}
+
+func TestUSTARLongName(t *testing.T) {
+ // Create an archive with a path that failed to split with USTAR extension in previous versions.
+ fileinfo, err := os.Stat("testdata/small.txt")
+ if err != nil {
+ t.Fatal(err)
+ }
+ hdr, err := FileInfoHeader(fileinfo, "")
+ hdr.Typeflag = TypeDir
+ if err != nil {
+ t.Fatalf("os.Stat:1 %v", err)
+ }
+ // Force a PAX long name to be written. The name was taken from a practical example
+ // that fails and replaced ever char through numbers to anonymize the sample.
+ longName := "/0000_0000000/00000-000000000/0000_0000000/00000-0000000000000/0000_0000000/00000-0000000-00000000/0000_0000000/00000000/0000_0000000/000/0000_0000000/00000000v00/0000_0000000/000000/0000_0000000/0000000/0000_0000000/00000y-00/0000/0000/00000000/0x000000/"
+ hdr.Name = longName
+
+ hdr.Size = 0
+ var buf bytes.Buffer
+ writer := NewWriter(&buf)
+ if err := writer.WriteHeader(hdr); err != nil {
+ t.Fatal(err)
+ }
+ if err := writer.Close(); err != nil {
+ t.Fatal(err)
+ }
+ // Test that we can get a long name back out of the archive.
+ reader := NewReader(&buf)
+ hdr, err = reader.Next()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if hdr.Name != longName {
+ t.Fatal("Couldn't recover long name")
+ }
+}
コアとなるコードの解説
src/pkg/archive/tar/writer.go
の変更点
splitUSTARLongName
関数は、長いファイル名を USTAR
形式の prefix
と name
フィールドに分割する役割を担っています。
-
nlen
の計算修正:- 変更前:
nlen := length - i - 1
- 変更後:
nlen := len(name) - i - 1
この修正により、name
フィールドに格納されるべきサフィックス部分の長さが正確に計算されるようになりました。len(name)
は文字列name
の実際のバイト長を返します。
- 変更前:
-
plen
の導入とチェック:- 新しく
plen := i
が導入されました。i
はstrings.LastIndex
によって見つけられた最後のスラッシュのインデックスであり、これはprefix
フィールドに格納されるべき部分の長さに相当します。 - エラー条件に
|| plen > fileNamePrefixSize
が追加されました。これにより、計算されたprefix
の長さがUSTAR
形式で許容される最大長 (155文字) を超えていないかどうかがチェックされるようになりました。もし超えている場合、errNameTooLong
エラーが返され、不正なアーカイブの作成が防止されます。
- 新しく
これらの変更により、splitUSTARLongName
関数は USTAR
形式の長期ファイル名に関する仕様をより厳密に遵守し、以前発生していた誤った分割やエラーを解消します。
src/pkg/archive/tar/writer_test.go
の変更点
TestUSTARLongName
という新しいテスト関数が追加されました。
-
テストデータの準備:
os.Stat("testdata/small.txt")
を使用して、既存の小さなファイルからFileInfo
を取得し、それを基にtar.Header
を作成しています。これは、テスト用のダミーのファイル情報として利用されます。hdr.Typeflag = TypeDir
と設定されており、ディレクトリとして扱われることを示しています。longName
という非常に長いパス文字列が定義されています。この文字列は、以前のバージョンでUSTAR
分割に失敗した実際の例を匿名化したものです。
-
アーカイブの書き込み:
bytes.Buffer
を使用してメモリ上にTarアーカイブを作成します。NewWriter
でtar.Writer
を作成し、writer.WriteHeader(hdr)
でlongName
を持つヘッダーを書き込みます。writer.Close()
で書き込みを完了します。
-
アーカイブの読み取りと検証:
NewReader
で作成したアーカイブを読み取るtar.Reader
を作成します。reader.Next()
で次のヘッダーを読み取ります。if hdr.Name != longName
で、読み取ったヘッダーのファイル名が元のlongName
と完全に一致するかどうかを検証します。一致しない場合はテストが失敗し、「Couldn't recover long name」というエラーが出力されます。
このテストは、splitUSTARLongName
の修正が正しく機能し、非常に長いファイル名が USTAR
形式でアーカイブされ、その後正しく復元できることをエンドツーエンドで確認するものです。これにより、バグが修正されただけでなく、将来のリグレッションを防ぐための安全網が提供されます。
関連リンク
- Go
archive/tar
パッケージのドキュメント: https://pkg.go.dev/archive/tar - Go CL 13300046 (このコミットの変更リスト): https://golang.org/cl/13300046
参考にした情報源リンク
- USTAR tar format long filename のWeb検索結果
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFXw4IEEztjPI16iZAsnR_ywJWxFkc-zcxU3RD5iRH91mNQX-KQmEYgMAJKRcpHrjaEZKeAYBrOXKARb3sQeeVSbZRyDaXkbhMKOt6RzC4tzOajJNA-2yVDzV2lrcfzUr22BJzriKZSDofvYpSVq40kf09i_2XvErp6n2SnE4M1WgP3Bb99
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG1XP4HnsZIBJ4c7OC7m1Aq_b97Zaf7KHqHZtiaCxgoPUnH6Yns8hS37WV7ZQCPnXFSBn-kFDJHnvePToyVjCCIox42xNGr60YpjblanUF5F2LQDoT6WHRFbGAyrN3HG1TKP5yymllGUwYyr-3lU_Ke7Mv1PE9Qg7srsYI=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFWZfX4vA6T0LCVBG0c656-WhB6Ys59p6ko84oHdVmlQAjgSCwXlRp09S916MnvkxRKSRSVyx31K5v0pRzokBF_EvINiffl5puf_ki2f90VLo3jF2YB4FN0u6LkCyA_LdaY9n2nCPcjLlfoIzRyJNFNQO1tJ0Swgs=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFG-G5PqOp_6KaI16EVHh2_mdSNgMO68s2u6GoPUyADfkqWar47jIoytSvxqsqgwwJo9XmtRQ9aQh4qVq1IQU6So0nKxODoFJzRDpVjmweqxy0844yjxV_q9DARrrNivE7kCRNQ58xpsAkwQttaVzM0w-1dH7LyuW1mKNAQ5SzyVyhla6ZDFnenYRhy6oFy
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFl318Oxl51qm5u09PY0wAQ0xtUFVS8k6KhngmUptLz-T5DvN9senmWQuZDsiQoN01bS4A0MF8e12yXkIPQukfKME4UcC21a_4OWGixV44vIXWEXdv7m6646xhrDEzTjHheEO6NE68OyZRhkWoAzv8tEzH42RHZcuo3iiiw4BWjU6QxXy2V
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHpQgq0uSGOLAXj7rXa-9k9ZPZ-7D2QOMhfLpDwBdBGrZb00pX7i-hK5BSaz-x8mY6wu3TCLCnMD4jpLfCGYA7j6ktUMIUskuvWA7i6dKHNDW1VkEeJwPFZF9-071lsyPfmnukPnJ53PIqP-ng=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEBA1vm5rACLK1VrEMgnkFTvofeyAgnppDs2uYr194jIswje_hiuEV13ddKU835m9D-lwHmX3iDK32gyW54g-4fB0GPCMtUgGmDVuOLyWnJyXCJMrJ--CXd8TWmV6bdTu1XjcVmYWMphiUT6KQQtMbSEARPsxejtnqI-5nsL9-Tehdl3wwl4g4zZASahzSonjohu6sqdGWUIdxjPZTLjh7IK62gqOoftwacelX4tG83aOtSiFNY
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGuGr2b5xr2AmHCLsBJs-M9KuvJ2NrsdxSneUhC8727XjeX-CNRyCvqhlUV-ce7EqO5h11TNiNCqyIUl32O3S5dvW-pFdcThIWP-D5gj7XuFQtE9H7GDmoaRkvbvQ0uvCCk9feksfaiVjoQCA7ypne
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF9kJYRjqdb5aNFksKuPnj8AThJ1ZkoTbvPcJkkGLvNYUPenRtVFbaAK9R2ZfuggNtgXnXBM15u8kV6ioAeXzR58f50DoC_HG3bnKsznI28MeAAFejV8_PmhdeIce0nSc7hLGTm74ADH1HcD6M13U91TIciqE9eoeUX9XqIBPYK_1U8mbySu6hWo1mYpcHeEhvEhQ==
- Tar (computing) - Wikipedia: https://en.wikipedia.org/wiki/Tar_(computing)
- USTAR - Wikipedia: https://en.wikipedia.org/wiki/Tar_(computing)#USTAR_format
- POSIX.1-1988 (USTAR) - The Open Group Base Specifications Issue 7: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_04
- Go言語の
strings.LastIndex
関数: https://pkg.go.dev/strings#LastIndex - Go言語の
len
関数: https://pkg.go.dev/builtin#len - Go言語の
os.Stat
関数: https://pkg.go.dev/os#Stat - Go言語の
bytes.Buffer
型: https://pkg.go.dev/bytes#Buffer - Go言語の
testing
パッケージ: https://pkg.go.dev/testing