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

[インデックス 13346] ファイルの概要

このコミットは、Go言語のsyscallパッケージにおけるWindows固有のcopyFindData関数のバグ修正に関するものです。具体的には、src/pkg/syscall/ztypes_windows.goファイル内のcopyFindData関数が修正されています。この関数は、Windows APIのWIN32_FIND_DATA構造体に関連するデータをコピーする際に、文字列の終端処理に関する誤った仮定を修正しています。

コミット

commit 1c4e20744a48ee0d7cdb74ed1cab5196345cf6a2
Author: Russ Cox <rsc@golang.org>
Date:   Wed Jun 13 16:44:19 2012 -0400

    syscall: fix windows copyFindData
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/6301076

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/1c4e20744a48ee0d7cdb74ed1cab5196345cf6a2

元コミット内容

syscall: fix windows copyFindData

変更の背景

この変更は、Go言語のsyscallパッケージがWindowsのファイル検索APIであるFindFirstFile/FindNextFileなどから返されるWIN32_FIND_DATA構造体を扱う際に発生していた潜在的なバグを修正するために行われました。

WIN32_FIND_DATA構造体には、ファイル名と代替ファイル名(8.3形式の短いファイル名)を格納するための固定長配列cFileNamecAlternateFileNameが含まれています。これらのフィールドは、C言語の文字列と同様にヌル終端されることが期待されます。

Goのsyscallパッケージでは、Windows APIから受け取った生の構造体(win32finddata1など、内部表現)を、Goのユーザーが扱いやすいようにラップした構造体(Win32finddata)に変換するcopyFindData関数が存在します。

元の実装では、src(生の構造体)のFileNameフィールドがdst(ラップされた構造体)のFileNameフィールドよりも1要素短いと誤解されており、dst.FileNameの最後の要素を明示的にゼロ(ヌル)に設定していました。同様に、src.AlternateFileNameについても誤ったヌル終端処理が行われていました。

この誤解により、ヌル終端が正しく行われない、または余分なゼロが書き込まれる可能性があり、ファイル名が正しく処理されない、あるいはバッファオーバーフローのような問題を引き起こす可能性がありました。このコミットは、この誤ったヌル終端処理の仮定を修正し、Windows APIの仕様に合致させることを目的としています。

前提知識の解説

1. Windows API WIN32_FIND_DATA構造体

WIN32_FIND_DATAは、Windowsのファイル検索関数(FindFirstFile, FindNextFileなど)がファイルやディレクトリの情報を返すために使用する構造体です。この構造体には、以下のような重要なフィールドが含まれます。

  • dwFileAttributes: ファイルの属性(読み取り専用、ディレクトリなど)。
  • ftCreationTime, ftLastAccessTime, ftLastWriteTime: ファイルの作成、最終アクセス、最終書き込み日時。
  • nFileSizeHigh, nFileSizeLow: ファイルサイズ。
  • cFileName[MAX_PATH]: ファイルのロングファイル名。MAX_PATHは通常260文字。
  • cAlternateFileName[14]: ファイルの8.3形式の短いファイル名。

cFileNamecAlternateFileNameは、固定長のWCHAR(ワイド文字、UTF-16)配列であり、C言語の文字列と同様にヌル終端(\0)されることが期待されます。つまり、文字列の実際の長さは、最初のヌル文字までのバイト数で決まります。

2. Go言語のsyscallパッケージ

Go言語のsyscallパッケージは、オペレーティングシステムが提供する低レベルのプリミティブ(システムコール)へのインターフェースを提供します。これにより、GoプログラムからOS固有の機能(ファイル操作、ネットワーク、プロセス管理など)を直接呼び出すことができます。

Windowsの場合、syscallパッケージはWin32 APIの関数や構造体をGoの型にマッピングし、Goプログラムからそれらを呼び出せるようにします。このマッピングには、C言語の構造体とGoの構造体の間のデータコピーや変換が含まれることがよくあります。

3. 固定長配列とヌル終端文字列

C言語では、文字列は通常、文字の配列として表現され、文字列の終わりを示すためにヌル文字(\0)が使用されます。WIN32_FIND_DATA構造体内のcFileNamecAlternateFileNameのようなフィールドは、固定長の配列ですが、その中に格納される文字列はヌル終端される必要があります。これは、配列のサイズが文字列の最大長を定義する一方で、実際の文字列の長さはヌル文字の位置によって決まることを意味します。

Goのcopy関数は、スライス間で要素をコピーしますが、ヌル終端の概念は持ちません。そのため、C言語のヌル終端文字列をGoのバイトスライスや文字列に変換する際には、ヌル文字の処理を明示的に行う必要があります。

技術的詳細

このコミットの技術的な核心は、GoのsyscallパッケージがWindows APIのWIN32_FIND_DATA構造体内の文字列フィールド(cFileNamecAlternateFileName)をGoの内部構造体にコピーする際の、ヌル終端に関する正確な理解と実装にあります。

元のコードでは、Win32finddata(Goの公開構造体)とwin32finddata1(Windows APIの内部構造体に対応するGoの型)の間でFileNameAlternateFileNameをコピーする際に、以下のようなコメントと処理がありました。

// The src is 1 element shorter than dst. Zero that last one.
copy(dst.FileName[:], src.FileName[:])
dst.FileName[len(dst.FileName)-1] = 0 // dstの最後の要素をゼロにする
copy(dst.AlternateFileName[:], src.AlternateFileName[:])
src.AlternateFileName[len(dst.AlternateFileName)-1] = 0 // srcの最後の要素をゼロにする (これは誤り)

このコメントとコードは、srcwin32finddata1のフィールド)がdstWin32finddataのフィールド)よりも1要素短いという誤った仮定に基づいていました。そして、その仮定に基づいて、dst.FileNameの最後の要素を明示的にゼロに設定していました。さらに、src.AlternateFileNameの最後の要素をゼロに設定するという、より深刻な誤りも含まれていました。これは、srcが読み取り元であるにもかかわらず、その内容を書き換えるという問題を引き起こす可能性がありました。

実際のところ、Windows APIのWIN32_FIND_DATA構造体内のcFileNamecAlternateFileNameは、Goのwin32finddata1構造体で表現される際に、ヌル終端文字を含む形で定義されています。つまり、Goのcopy関数でsrc.FileName[:]からdst.FileName[:]へコピーするだけで、ヌル終端文字も正しくコピーされるはずです。

このコミットは、この誤解を修正し、srcdstの配列サイズが実際には同じか、srcdstよりも大きい(ただし、ヌル終端されているため問題ない)という正しい理解に基づいています。したがって、明示的なヌル終端処理は不要であり、むしろ誤った動作を引き起こす可能性がありました。

修正後のコードでは、コメントが以下のように変更され、明示的なヌル終端処理の行が削除されました。

// The src is 1 element bigger than dst, but it must be NUL.
copy(dst.FileName[:], src.FileName[:])
copy(dst.AlternateFileName[:], src.AlternateFileName[:])

この変更は、copy関数がヌル終端文字を含むすべてのバイトを正しくコピーすることを信頼し、余分な操作を削除することで、コードをより正確で堅牢なものにしています。これにより、Windowsのファイル名がGoプログラムで正しく扱われるようになります。

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

変更はsrc/pkg/syscall/ztypes_windows.goファイル内のcopyFindData関数に集中しています。

--- a/src/pkg/syscall/ztypes_windows.go
+++ b/src/pkg/syscall/ztypes_windows.go
@@ -376,11 +376,9 @@ func copyFindData(dst *Win32finddata, src *win32finddata1) {
 	dst.Reserved0 = src.Reserved0
 	dst.Reserved1 = src.Reserved1
 
-	// The src is 1 element shorter than dst. Zero that last one.
+	// The src is 1 element bigger than dst, but it must be NUL.
 	copy(dst.FileName[:], src.FileName[:])
-	dst.FileName[len(dst.FileName)-1] = 0
 	copy(dst.AlternateFileName[:], src.AlternateFileName[:])
-	src.AlternateFileName[len(dst.AlternateFileName)-1] = 0
 }

具体的には、以下の2行が削除されました。

  1. dst.FileName[len(dst.FileName)-1] = 0
  2. src.AlternateFileName[len(dst.AlternateFileName)-1] = 0

そして、コメントが変更されました。

コアとなるコードの解説

copyFindData関数は、Windows APIから直接取得したwin32finddata1型のデータ(src)を、Goのアプリケーション層で利用しやすいWin32finddata型のデータ(dst)に変換する役割を担っています。

削除された2行は、それぞれFileNameAlternateFileNameという文字列フィールドのヌル終端を明示的に行おうとしていました。

  • dst.FileName[len(dst.FileName)-1] = 0: これは、dst.FileNameの最後の要素をゼロに設定することで、ヌル終端を保証しようとするものでした。しかし、copy(dst.FileName[:], src.FileName[:])が正しくヌル終端文字をコピーしていれば、この行は不要であり、場合によっては誤った位置にヌルを書き込む可能性がありました。
  • src.AlternateFileName[len(dst.AlternateFileName)-1] = 0: これはさらに問題で、読み取り元であるsrcのデータを変更しようとしていました。これは、srcが不変であるべきという原則に反し、予期せぬ副作用を引き起こす可能性がありました。

新しいコメント// The src is 1 element bigger than dst, but it must be NUL.は、win32finddata1のフィールドがWin32finddataのフィールドよりもサイズが大きい場合があるが、それはヌル終端文字を含むためであり、copy関数が正しく機能することを意味しています。つまり、copy関数がヌル終端文字を含めてすべての関連バイトをコピーするため、明示的なヌル終端処理は不要であり、むしろ誤った動作を引き起こす可能性があったということです。

この修正により、copyFindData関数は、Windows APIのWIN32_FIND_DATA構造体からGoの構造体へのデータコピーを、より正確かつ安全に行うことができるようになりました。

関連リンク

参考にした情報源リンク

  • 上記のGitHubコミットページ
  • Go言語の公式ドキュメント
  • Microsoft LearnのWindows APIドキュメント
  • Go言語のsyscallパッケージのソースコード(特にztypes_windows.go
  • Go言語のコードレビュープロセスに関する一般的な知識
  • C言語における文字列とヌル終端の概念に関する一般的な知識
  • Go言語のcopy関数の動作に関する一般的な知識
  • Go言語のsyscallパッケージの歴史的な変更履歴(git blameなど)
  • Go言語のIssueトラッカーやメーリングリスト(golang.org/cl/6301076が参照しているGoのコードレビューシステム)# [インデックス 13346] ファイルの概要

このコミットは、Go言語のsyscallパッケージにおけるWindows固有のcopyFindData関数のバグ修正に関するものです。具体的には、src/pkg/syscall/ztypes_windows.goファイル内のcopyFindData関数が修正されています。この関数は、Windows APIのWIN32_FIND_DATA構造体に関連するデータをコピーする際に、文字列の終端処理に関する誤った仮定を修正しています。

コミット

commit 1c4e20744a48ee0d7cdb74ed1cab5196345cf6a2
Author: Russ Cox <rsc@golang.org>
Date:   Wed Jun 13 16:44:19 2012 -0400

    syscall: fix windows copyFindData
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/6301076

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/1c4e20744a48ee0d7cdb74ed1cab5196345cf6a2

元コミット内容

syscall: fix windows copyFindData

変更の背景

この変更は、Go言語のsyscallパッケージがWindowsのファイル検索APIであるFindFirstFile/FindNextFileなどから返されるWIN32_FIND_DATA構造体を扱う際に発生していた潜在的なバグを修正するために行われました。

WIN32_FIND_DATA構造体には、ファイル名と代替ファイル名(8.3形式の短いファイル名)を格納するための固定長配列cFileNamecAlternateFileNameが含まれています。これらのフィールドは、C言語の文字列と同様にヌル終端されることが期待されます。

Goのsyscallパッケージでは、Windows APIから受け取った生の構造体(win32finddata1など、内部表現)を、Goのユーザーが扱いやすいようにラップした構造体(Win32finddata)に変換するcopyFindData関数が存在します。

元の実装では、src(生の構造体)のFileNameフィールドがdst(ラップされた構造体)のFileNameフィールドよりも1要素短いと誤解されており、dst.FileNameの最後の要素を明示的にゼロ(ヌル)に設定していました。同様に、src.AlternateFileNameについても誤ったヌル終端処理が行われていました。

この誤解により、ヌル終端が正しく行われない、または余分なゼロが書き込まれる可能性があり、ファイル名が正しく処理されない、あるいはバッファオーバーフローのような問題を引き起こす可能性がありました。このコミットは、この誤ったヌル終端処理の仮定を修正し、Windows APIの仕様に合致させることを目的としています。

前提知識の解説

1. Windows API WIN32_FIND_DATA構造体

WIN32_FIND_DATAは、Windowsのファイル検索関数(FindFirstFile, FindNextFileなど)がファイルやディレクトリの情報を返すために使用する構造体です。この構造体には、以下のような重要なフィールドが含まれます。

  • dwFileAttributes: ファイルの属性(読み取り専用、ディレクトリなど)。
  • ftCreationTime, ftLastAccessTime, ftLastWriteTime: ファイルの作成、最終アクセス、最終書き込み日時。
  • nFileSizeHigh, nFileSizeLow: ファイルサイズ。
  • cFileName[MAX_PATH]: ファイルのロングファイル名。MAX_PATHは通常260文字。
  • cAlternateFileName[14]: ファイルの8.3形式の短いファイル名。

cFileNamecAlternateFileNameは、固定長のWCHAR(ワイド文字、UTF-16)配列であり、C言語の文字列と同様にヌル終端(\0)されることが期待されます。つまり、文字列の実際の長さは、最初のヌル文字までのバイト数で決まります。

2. Go言語のsyscallパッケージ

Go言語のsyscallパッケージは、オペレーティングシステムが提供する低レベルのプリミティブ(システムコール)へのインターフェースを提供します。これにより、GoプログラムからOS固有の機能(ファイル操作、ネットワーク、プロセス管理など)を直接呼び出すことができます。

Windowsの場合、syscallパッケージはWin32 APIの関数や構造体をGoの型にマッピングし、Goプログラムからそれらを呼び出せるようにします。このマッピングには、C言語の構造体とGoの構造体の間のデータコピーや変換が含まれることがよくあります。

3. 固定長配列とヌル終端文字列

C言語では、文字列は通常、文字の配列として表現され、文字列の終わりを示すためにヌル文字(\0)が使用されます。WIN32_FIND_DATA構造体内のcFileNamecAlternateFileNameのようなフィールドは、固定長の配列ですが、その中に格納される文字列はヌル終端される必要があります。これは、配列のサイズが文字列の最大長を定義する一方で、実際の文字列の長さはヌル文字の位置によって決まることを意味します。

Goのcopy関数は、スライス間で要素をコピーしますが、ヌル終端の概念は持ちません。そのため、C言語のヌル終端文字列をGoのバイトスライスや文字列に変換する際には、ヌル文字の処理を明示的に行う必要があります。

技術的詳細

このコミットの技術的な核心は、GoのsyscallパッケージがWindows APIのWIN32_FIND_DATA構造体内の文字列フィールド(cFileNamecAlternateFileName)をGoの内部構造体にコピーする際の、ヌル終端に関する正確な理解と実装にあります。

元のコードでは、Win32finddata(Goの公開構造体)とwin32finddata1(Windows APIの内部構造体に対応するGoの型)の間でFileNameAlternateFileNameをコピーする際に、以下のようなコメントと処理がありました。

// The src is 1 element shorter than dst. Zero that last one.
copy(dst.FileName[:], src.FileName[:])
dst.FileName[len(dst.FileName)-1] = 0 // dstの最後の要素をゼロにする
copy(dst.AlternateFileName[:], src.AlternateFileName[:])
src.AlternateFileName[len(dst.AlternateFileName)-1] = 0 // srcの最後の要素をゼロにする (これは誤り)

このコメントとコードは、srcwin32finddata1のフィールド)がdstWin32finddataのフィールド)よりも1要素短いという誤った仮定に基づいていました。そして、その仮定に基づいて、dst.FileNameの最後の要素を明示的にゼロに設定していました。さらに、src.AlternateFileNameの最後の要素をゼロに設定するという、より深刻な誤りも含まれていました。これは、srcが読み取り元であるにもかかわらず、その内容を書き換えるという問題を引き起こす可能性がありました。

実際のところ、Windows APIのWIN32_FIND_DATA構造体内のcFileNamecAlternateFileNameは、Goのwin32finddata1構造体で表現される際に、ヌル終端文字を含む形で定義されています。つまり、Goのcopy関数でsrc.FileName[:]からdst.FileName[:]へコピーするだけで、ヌル終端文字も正しくコピーされるはずです。

このコミットは、この誤解を修正し、srcdstの配列サイズが実際には同じか、srcdstよりも大きい(ただし、ヌル終端されているため問題ない)という正しい理解に基づいています。したがって、明示的なヌル終端処理は不要であり、むしろ誤った動作を引き起こす可能性がありました。

修正後のコードでは、コメントが以下のように変更され、明示的なヌl終端処理の行が削除されました。

// The src is 1 element bigger than dst, but it must be NUL.
copy(dst.FileName[:], src.FileName[:])
copy(dst.AlternateFileName[:], src.AlternateFileName[:])

この変更は、copy関数がヌル終端文字を含むすべてのバイトを正しくコピーすることを信頼し、余分な操作を削除することで、コードをより正確で堅牢なものにしています。これにより、Windowsのファイル名がGoプログラムで正しく扱われるようになります。

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

変更はsrc/pkg/syscall/ztypes_windows.goファイル内のcopyFindData関数に集中しています。

--- a/src/pkg/syscall/ztypes_windows.go
+++ b/src/pkg/syscall/ztypes_windows.go
@@ -376,11 +376,9 @@ func copyFindData(dst *Win32finddata, src *win32finddata1) {
 	dst.Reserved0 = src.Reserved0
 	dst.Reserved1 = src.Reserved1
 
-	// The src is 1 element shorter than dst. Zero that last one.
+	// The src is 1 element bigger than dst, but it must be NUL.
 	copy(dst.FileName[:], src.FileName[:])
-	dst.FileName[len(dst.FileName)-1] = 0
 	copy(dst.AlternateFileName[:], src.AlternateFileName[:])
-	src.AlternateFileName[len(dst.AlternateFileName)-1] = 0
 }

具体的には、以下の2行が削除されました。

  1. dst.FileName[len(dst.FileName)-1] = 0
  2. src.AlternateFileName[len(dst.AlternateFileName)-1] = 0

そして、コメントが変更されました。

コアとなるコードの解説

copyFindData関数は、Windows APIから直接取得したwin32finddata1型のデータ(src)を、Goのアプリケーション層で利用しやすいWin32finddata型のデータ(dst)に変換する役割を担っています。

削除された2行は、それぞれFileNameAlternateFileNameという文字列フィールドのヌル終端を明示的に行おうとしていました。

  • dst.FileName[len(dst.FileName)-1] = 0: これは、dst.FileNameの最後の要素をゼロに設定することで、ヌル終端を保証しようとするものでした。しかし、copy(dst.FileName[:], src.FileName[:])が正しくヌル終端文字をコピーしていれば、この行は不要であり、場合によっては誤った位置にヌルを書き込む可能性がありました。
  • src.AlternateFileName[len(dst.AlternateFileName)-1] = 0: これはさらに問題で、読み取り元であるsrcのデータを変更しようとしていました。これは、srcが不変であるべきという原則に反し、予期せぬ副作用を引き起こす可能性がありました。

新しいコメント// The src is 1 element bigger than dst, but it must be NUL.は、win32finddata1のフィールドがWin32finddataのフィールドよりもサイズが大きい場合があるが、それはヌル終端文字を含むためであり、copy関数が正しく機能することを意味しています。つまり、copy関数がヌル終端文字を含めてすべての関連バイトをコピーするため、明示的なヌル終端処理は不要であり、むしろ誤った動作を引き起こす可能性があったということです。

この修正により、copyFindData関数は、Windows APIのWIN32_FIND_DATA構造体からGoの構造体へのデータコピーを、より正確かつ安全に行うことができるようになりました。

関連リンク

参考にした情報源リンク

  • 上記のGitHubコミットページ
  • Go言語の公式ドキュメント
  • Microsoft LearnのWindows APIドキュメント
  • Go言語のsyscallパッケージのソースコード(特にztypes_windows.go
  • Go言語のコードレビュープロセスに関する一般的な知識
  • C言語における文字列とヌル終端の概念に関する一般的な知識
  • Go言語のcopy関数の動作に関する一般的な知識
  • Go言語のIssueトラッカーやメーリングリスト(golang.org/cl/6301076が参照しているGoのコードレビューシステム)