[インデックス 1023] ファイルの概要
このコミットは、Go言語の初期開発段階におけるリンカ(6l
)のコードベースに対する変更を記録しています。具体的には、文字列フォーマット関数 sprint
の使用箇所を、より安全な snprint
に置き換えることで、潜在的なバッファオーバーフローの脆弱性に対処しています。
コミット
commit 6fff0efdd8520a2128e116ce881b1f4cd3c6df27
Author: Ken Thompson <ken@golang.org>
Date: Sat Nov 1 15:56:06 2008 -0700
sprint changed to snprint
R=r
OCL=18316
CL=18316
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6fff0efdd8520a2128e116ce881b1f4cd3c6df27
元コミット内容
このコミットの元のメッセージは非常に簡潔で、「sprint changed to snprint」とだけ記されています。これは、コード内で使用されている sprint
関数が snprint
関数に変更されたことを示しています。R=r
はレビュー担当者を示し、OCL
と CL
は内部的な変更リスト番号を指します。
変更の背景
この変更の背景には、C言語における文字列操作関数の安全性に関する一般的な課題があります。特に sprintf
(そして、このコミットで言及されている sprint
も同様の特性を持つと推測されます) のような関数は、出力先のバッファサイズを考慮せずに文字列を書き込むため、生成される文字列がバッファの容量を超えた場合にバッファオーバーフローを引き起こす可能性があります。バッファオーバーフローは、プログラムのクラッシュ、予期せぬ動作、さらには悪意のあるコード実行につながる深刻なセキュリティ脆弱性となることがあります。
snprintf
(そして、このコミットで言及されている snprint
も同様の特性を持つと推測されます) は、出力先のバッファサイズを引数として受け取ることで、この問題を解決します。これにより、関数は指定されたバッファサイズを超えて書き込むことを防ぎ、バッファオーバーフローのリスクを軽減します。
Go言語の初期開発段階において、リンカのような低レベルのツールはC言語で記述されていました。このようなツールでは、パフォーマンスとリソース効率が重要である一方で、セキュリティと堅牢性も同様に重要です。したがって、潜在的なバッファオーバーフローの脆弱性を早期に特定し、修正することは、Goツールチェイン全体の安定性とセキュリティを確保するために不可欠でした。
前提知識の解説
C言語における文字列フォーマット関数
C言語には、書式指定文字列に基づいて文字列を生成するためのいくつかの関数があります。
sprintf
:stdio.h
ヘッダで定義されており、指定された書式に従って文字列をフォーマットし、指定された文字配列(バッファ)に書き込みます。しかし、バッファのサイズをチェックしないため、バッファオーバーフローの危険性があります。snprintf
:stdio.h
ヘッダで定義されており、sprintf
と同様に文字列をフォーマットしますが、書き込む最大文字数を指定する引数size
を持ちます。これにより、バッファオーバーフローを防ぐことができます。snprintf
は、size - 1
文字までをバッファに書き込み、最後にヌル終端文字を追加します。
このコミットで言及されている sprint
と snprint
は、Go言語の初期ツールチェイン内で使用されていたカスタムの文字列フォーマット関数である可能性が高いです。これらは標準Cライブラリの sprintf
と snprintf
に似た機能を提供していたと考えられます。特に snprint
は、snprintf
と同様にバッファサイズを考慮する安全なバージョンとして導入されたと推測されます。
Go言語のリンカ (6l
)
Go言語のツールチェインは、コンパイラ、アセンブラ、リンカなど、複数のコンポーネントで構成されています。このコミットで変更されている src/cmd/6l/list.c
は、Go言語のリンカの一部です。
- リンカ: コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含む)を結合し、実行可能なプログラムを生成するツールです。リンカは、異なるオブジェクトファイル間の参照を解決し、必要なライブラリをリンクします。
6l
: Go言語の初期のリンカの一つで、特に64ビットアーキテクチャ(x86-64、または当時のGoのターゲットアーキテクチャ)向けのリンカを指します。Go言語のツールチェインでは、ターゲットアーキテクチャに応じて異なるリンカが使用されることがありました(例:8l
は32ビットアーキテクチャ向け)。
list.c
ファイルは、リンカのデバッグや診断出力に関連するコードを含んでいる可能性が高いです。リンカが処理するプログラムの構造やシンボル情報を整形して表示するために、文字列フォーマット関数が頻繁に使用されます。
技術的詳細
このコミットの技術的詳細は、sprint
から snprint
への置き換えが、コードの堅牢性とセキュリティを向上させるための標準的なプラクティスに従っている点にあります。
sprint
の呼び出しは、以下のように変更されています。
- sprint(str, "...");
+ snprint(str, sizeof(str), "...");
ここで、str
は文字列を格納するためのバッファであり、sizeof(str)
はそのバッファのサイズをバイト単位で返します。snprint
は、この sizeof(str)
を超えて str
に書き込むことを防ぎます。これにより、フォーマットされた文字列がバッファの容量を超えても、隣接するメモリ領域を破壊するバッファオーバーフローが発生しなくなります。
この変更は、src/cmd/6l/list.c
ファイル内の複数の箇所で行われています。このファイルは、リンカがプログラムの構造(命令、データ、シンボルなど)をリスト表示する際に使用するフォーマットロジックを含んでいます。例えば、Pconv
関数はプログラムの命令(Prog
)を文字列に変換し、Dconv
関数はデータやアドレスの情報を文字列に変換します。これらの関数内で、様々な種類の情報(行番号、命令の種類、アドレス、シンボル名など)を整形して表示するために sprint
が使用されていました。
この修正は、リンカの出力生成部分における潜在的な脆弱性を排除し、ツールチェイン全体の信頼性を高める上で重要なステップでした。
コアとなるコードの変更箇所
変更は src/cmd/6l/list.c
ファイルに集中しており、sprint
関数の呼び出しが snprint
に置き換えられています。
具体的には、以下のパターンで変更が行われています。
--- a/src/cmd/6l/list.c
+++ b/src/cmd/6l/list.c
@@ -52,27 +52,27 @@ Pconv(Fmt *fp)
p = va_arg(fp->args, Prog*);
bigP = p;
- sprint(str1, "(%ld)", p->line);
+ snprint(str1, sizeof(str1), "(%ld)", p->line);
switch(p->as) {
case ATEXT:
if(p->from.scale) {
- sprint(str, "%-7s %-7A %D,%d,%lD",
+ snprint(str, sizeof(str), "%-7s %-7A %D,%d,%lD",
str1, p->as, &p->from, p->from.scale, &p->to);
break;
}
- sprint(str, "%-7s %-7A %D,%lD",
+ snprint(str, sizeof(str), "%-7s %-7A %D,%lD",
str1, p->as, &p->from, &p->to);
break;
default:
- sprint(str, "%-7s %-7A %D,%D",
+ snprint(str, sizeof(str), "%-7s %-7A %D,%D",
str1, p->as, &p->from, &p->to);
break;
case ADATA:
case AINIT:
case ADYNT:
- sprint(str, "%-7s %-7A %D/%d,%D",
+ snprint(str, sizeof(str), "%-7s %-7A %D/%d,%D",
str1, p->as, &p->from, p->from.scale, &p->to);
break;
}
@@ -102,32 +102,32 @@ Dconv(Fmt *fp)
if(fp->flags & FmtLong) {
if(i != D_CONST) {
// ATEXT dst is not constant
- sprint(str, "!!%D", a);
+ snprint(str, sizeof(str), "!!%D", a);
goto brk;
}
parsetextconst(a->offset);
if(textarg == 0) {
- sprint(str, "$%lld", textstksiz);
+ snprint(str, sizeof(str), "$%lld", textstksiz);
goto brk;
}
- sprint(str, "$%lld-%lld", textstksiz, textarg);
+ snprint(str, sizeof(str), "$%lld-%lld", textstksiz, textarg);
goto brk;
}
if(i >= D_INDIR) {
if(a->offset)
- sprint(str, "%lld(%R)", a->offset, i-D_INDIR);
+ snprint(str, sizeof(str), "%lld(%R)", a->offset, i-D_INDIR);
else
- sprint(str, "(%R)", i-D_INDIR);
+ snprint(str, sizeof(str), "(%R)", i-D_INDIR);
goto brk;
}
switch(i) {
default:
if(a->offset)
- sprint(str, "$%lld,%R", a->offset, i);
+ snprint(str, sizeof(str), "$%lld,%R", a->offset, i);
else
- sprint(str, "%R", i);
+ snprint(str, sizeof(str), "%R", i);
break;
case D_NONE:
@@ -137,70 +137,70 @@ Dconv(Fmt *fp)
case D_BRANCH:
if(bigP != P && bigP->pcond != P)
if(a->sym != S)
- sprint(str, "%llux+%s", bigP->pcond->pc,
+ snprint(str, sizeof(str), "%llux+%s", bigP->pcond->pc,
a->sym->name);
else
- sprint(str, "%llux", bigP->pcond->pc);
+ snprint(str, sizeof(str), "%llux", bigP->pcond->pc);
else
- sprint(str, "%lld(PC)", a->offset);
+ snprint(str, sizeof(str), "%lld(PC)", a->offset);
break;
case D_EXTERN:
if(a->sym) {
- sprint(str, "%s+%lld(SB)", a->sym->name, a->offset);
+ snprint(str, sizeof(str), "%s+%lld(SB)", a->sym->name, a->offset);
break;
}
- sprint(str, "!!noname!!+%lld(SB)", a->offset);
+ snprint(str, sizeof(str), "!!noname!!+%lld(SB)", a->offset);
break;
case D_STATIC:
if(a->sym) {
- sprint(str, "%s<%d>+%lld(SB)", a->sym->name,
+ snprint(str, sizeof(str), "%s<%d>+%lld(SB)", a->sym->name,
a->sym->version, a->offset);
break;
}
- sprint(str, "!!noname!!<999>+%lld(SB)", a->offset);
+ snprint(str, sizeof(str), "!!noname!!<999>+%lld(SB)", a->offset);
break;
case D_AUTO:
if(a->sym) {
- sprint(str, "%s+%lld(SP)", a->sym->name, a->offset);
+ snprint(str, sizeof(str), "%s+%lld(SP)", a->sym->name, a->offset);
break;
}
- sprint(str, "!!noname!!+%lld(SP)", a->offset);
+ snprint(str, sizeof(str), "!!noname!!+%lld(SP)", a->offset);
break;
case D_PARAM:
if(a->sym) {
- sprint(str, "%s+%lld(%s)", a->sym->name, a->offset, paramspace);
+ snprint(str, sizeof(str), "%s+%lld(%s)", a->sym->name, a->offset, paramspace);
break;
}
- sprint(str, "!!noname!!+%lld(%s)", a->offset, paramspace);
+ snprint(str, sizeof(str), "!!noname!!+%lld(%s)", a->offset, paramspace);
break;
case D_CONST:
- sprint(str, "$%lld", a->offset);
+ snprint(str, sizeof(str), "$%lld", a->offset);
break;
case D_FCONST:
- sprint(str, "$(%.8lux,%.8lux)", a->ieee.h, a->ieee.l);
+ snprint(str, sizeof(str), "$(%.8lux,%.8lux)", a->ieee.h, a->ieee.l);
break;
case D_SCONST:
- sprint(str, "$\"%S\"", a->scon);
+ snprint(str, sizeof(str), "$\"%S\"", a->scon);
break;
case D_ADDR:
a->type = a->index;
a->index = D_NONE;
- sprint(str, "$%D", a);
+ snprint(str, sizeof(str), "$%D", a);
a->index = a->type;
a->type = D_ADDR;
goto conv;
}
brk:
if(a->index != D_NONE) {
- sprint(s, "(%R*%d)", a->index, a->scale);
+ snprint(s, sizeof(s), "(%R*%d)", a->index, a->scale);
strcat(str, s);
}
conv:
@@ -342,9 +342,9 @@ Rconv(Fmt *fp)
r = va_arg(fp->args, int);
if(r >= D_AL && r <= D_NONE)
- sprint(str, "%s", regstr[r-D_AL]);
+ snprint(str, sizeof(str), "%s", regstr[r-D_AL]);
else
- sprint(str, "gok(%d)", r);
+ snprint(str, sizeof(str), "gok(%d)", r);
return fmtstrcpy(fp, str);
}
コアとなるコードの解説
このコミットの核心は、sprint
関数を snprint
関数に置き換えることで、文字列フォーマット処理におけるバッファオーバーフローの脆弱性を排除した点にあります。
src/cmd/6l/list.c
ファイルは、Go言語のリンカ 6l
の一部であり、主にリンカのデバッグ出力やリスト表示に関連する機能を提供しています。このファイルには、Pconv
や Dconv
といった関数が含まれており、これらはリンカが処理するプログラムの命令(Prog
)やデータ(Addr
)構造体を、人間が読める形式の文字列に変換する役割を担っています。
変更前は、これらの変換処理において sprint
関数が使用されていました。sprint
は、C標準ライブラリの sprintf
と同様に、出力先のバッファサイズを考慮せずに文字列を書き込むため、フォーマットされる文字列がバッファの容量を超えた場合に、隣接するメモリ領域を上書きしてしまう可能性がありました。これは、プログラムのクラッシュや、悪意のあるコード実行につながるセキュリティ上の深刻な問題です。
変更後、すべての sprint
の呼び出しは snprint
に置き換えられ、第二引数として sizeof(str)
または sizeof(str1)
が追加されています。ここで str
や str1
は、フォーマットされた文字列を格納するための固定サイズの文字配列(バッファ)です。sizeof()
演算子は、このバッファの合計サイズをバイト単位で返します。
snprint
関数は、この sizeof()
で指定されたサイズを超えてバッファに書き込むことを防ぎます。これにより、たとえフォーマットされる文字列が非常に長くなったとしても、バッファオーバーフローが発生するリスクがなくなります。これは、リンカのようなシステムレベルのツールにおいて、堅牢性とセキュリティを確保するために非常に重要な変更です。
この修正は、Go言語の初期段階からセキュリティと安定性への配慮がなされていたことを示しており、後のGo言語の設計思想にも通じるものです。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語の初期の歴史に関する情報(Goの設計思想や初期のツールチェインについて理解を深めるのに役立つ可能性があります)
参考にした情報源リンク
- C言語の
sprintf
とsnprintf
に関するドキュメントやチュートリアル(例: cppreference.com, man pages) - バッファオーバーフローに関するセキュリティ情報
- Go言語のツールチェインの構造に関するドキュメント(特に初期のGoに関するもの)
- Go言語のリンカに関する技術記事や解説
[インデックス 1023] ファイルの概要
このコミットは、Go言語の初期開発段階におけるリンカ(6l
)のコードベースに対する変更を記録しています。具体的には、文字列フォーマット関数 sprint
の使用箇所を、より安全な snprint
に置き換えることで、潜在的なバッファオーバーフローの脆弱性に対処しています。
コミット
commit 6fff0efdd8520a2128e116ce881b1f4cd3c6df27
Author: Ken Thompson <ken@golang.org>
Date: Sat Nov 1 15:56:06 2008 -0700
sprint changed to snprint
R=r
OCL=18316
CL=18316
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6fff0efdd8520a2128e116ce881b1f4cd3c6df27
元コミット内容
このコミットの元のメッセージは非常に簡潔で、「sprint changed to snprint」とだけ記されています。これは、コード内で使用されている sprint
関数が snprint
関数に変更されたことを示しています。R=r
はレビュー担当者を示し、OCL
と CL
は内部的な変更リスト番号を指します。
変更の背景
この変更の背景には、C言語における文字列操作関数の安全性に関する一般的な課題があります。特に sprintf
(そして、このコミットで言及されている sprint
も同様の特性を持つと推測されます) のような関数は、出力先のバッファサイズを考慮せずに文字列を書き込むため、生成される文字列がバッファの容量を超えた場合にバッファオーバーフローを引き起こす可能性があります。バッファオーバーフローは、プログラムのクラッシュ、予期せぬ動作、さらには悪意のあるコード実行につながる深刻なセキュリティ脆弱性となることがあります。
snprintf
(そして、このコミットで言及されている snprint
も同様の特性を持つと推測されます) は、出力先のバッファサイズを引数として受け取ることで、この問題を解決します。これにより、関数は指定されたバッファサイズを超えて書き込むことを防ぎ、バッファオーバーフローのリスクを軽減します。
Go言語の初期開発段階において、リンカのような低レベルのツールはC言語で記述されていました。このようなツールでは、パフォーマンスとリソース効率が重要である一方で、セキュリティと堅牢性も同様に重要です。したがって、潜在的なバッファオーバーフローの脆弱性を早期に特定し、修正することは、Goツールチェイン全体の安定性とセキュリティを確保するために不可欠でした。
前提知識の解説
C言語における文字列フォーマット関数
C言語には、書式指定文字列に基づいて文字列を生成するためのいくつかの関数があります。
sprintf
:stdio.h
ヘッダで定義されており、指定された書式に従って文字列をフォーマットし、指定された文字配列(バッファ)に書き込みます。しかし、バッファのサイズをチェックしないため、バッファオーバーフローの危険性があります。snprintf
:stdio.h
ヘッダで定義されており、sprintf
と同様に文字列をフォーマットしますが、書き込む最大文字数を指定する引数size
を持ちます。これにより、バッファオーバーフローを防ぐことができます。snprintf
は、size - 1
文字までをバッファに書き込み、最後にヌル終端文字を追加します。
このコミットで言及されている sprint
と snprint
は、Go言語の初期ツールチェイン内で使用されていたカスタムの文字列フォーマット関数である可能性が高いです。これらは標準Cライブラリの sprintf
と snprintf
に似た機能を提供していたと考えられます。特に snprint
は、snprintf
と同様にバッファサイズを考慮する安全なバージョンとして導入されたと推測されます。
Go言語のリンカ (6l
)
Go言語のツールチェインは、コンパイラ、アセンブラ、リンカなど、複数のコンポーネントで構成されています。このコミットで変更されている src/cmd/6l/list.c
は、Go言語のリンカの一部です。
- リンカ: コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含む)を結合し、実行可能なプログラムを生成するツールです。リンカは、異なるオブジェクトファイル間の参照を解決し、必要なライブラリをリンクします。
6l
: Go言語の初期のリンカの一つで、特に64ビットアーキテクチャ(x86-64、または当時のGoのターゲットアーキテクチャ)向けのリンカを指します。Go言語のツールチェインでは、ターゲットアーキテクチャに応じて異なるリンカが使用されることがありました(例:8l
は32ビットアーキテクチャ向け)。
list.c
ファイルは、リンカのデバッグや診断出力に関連するコードを含んでいる可能性が高いです。リンカが処理するプログラムの構造やシンボル情報を整形して表示するために、文字列フォーマット関数が頻繁に使用されます。
技術的詳細
このコミットの技術的詳細は、sprint
から snprint
への置き換えが、コードの堅牢性とセキュリティを向上させるための標準的なプラクティスに従っている点にあります。
sprint
の呼び出しは、以下のように変更されています。
- sprint(str, "...");
+ snprint(str, sizeof(str), "...");
ここで、str
は文字列を格納するためのバッファであり、sizeof(str)
はそのバッファのサイズをバイト単位で返します。snprint
は、この sizeof(str)
を超えて str
に書き込むことを防ぎます。これにより、フォーマットされた文字列がバッファの容量を超えても、隣接するメモリ領域を破壊するバッファオーバーフローが発生しなくなります。
この変更は、src/cmd/6l/list.c
ファイル内の複数の箇所で行われています。このファイルは、リンカがプログラムの構造(命令、データ、シンボルなど)をリスト表示する際に使用するフォーマットロジックを含んでいます。例えば、Pconv
関数はプログラムの命令(Prog
)を文字列に変換し、Dconv
関数はデータやアドレスの情報を文字列に変換します。これらの関数内で、様々な種類の情報(行番号、命令の種類、アドレス、シンボル名など)を整形して表示するために sprint
が使用されていました。
この修正は、リンカの出力生成部分における潜在的な脆弱性を排除し、ツールチェイン全体の信頼性を高める上で重要なステップでした。
コアとなるコードの変更箇所
変更は src/cmd/6l/list.c
ファイルに集中しており、sprint
関数の呼び出しが snprint
に置き換えられています。
具体的には、以下のパターンで変更が行われています。
--- a/src/cmd/6l/list.c
+++ b/src/cmd/6l/list.c
@@ -52,27 +52,27 @@ Pconv(Fmt *fp)
p = va_arg(fp->args, Prog*);
bigP = p;
- sprint(str1, "(%ld)", p->line);
+ snprint(str1, sizeof(str1), "(%ld)", p->line);
switch(p->as) {
case ATEXT:
if(p->from.scale) {
- sprint(str, "%-7s %-7A %D,%d,%lD",
+ snprint(str, sizeof(str), "%-7s %-7A %D,%d,%lD",
str1, p->as, &p->from, p->from.scale, &p->to);
break;
}
- sprint(str, "%-7s %-7A %D,%lD",
+ snprint(str, sizeof(str), "%-7s %-7A %D,%lD",
str1, p->as, &p->from, &p->to);
break;
default:
- sprint(str, "%-7s %-7A %D,%D",
+ snprint(str, sizeof(str), "%-7s %-7A %D,%D",
str1, p->as, &p->from, &p->to);
break;
case ADATA:
case AINIT:
case ADYNT:
- sprint(str, "%-7s %-7A %D/%d,%D",
+ snprint(str, sizeof(str), "%-7s %-7A %D/%d,%D",
str1, p->as, &p->from, p->from.scale, &p->to);
break;
}
@@ -102,32 +102,32 @@ Dconv(Fmt *fp)
if(fp->flags & FmtLong) {
if(i != D_CONST) {
// ATEXT dst is not constant
- sprint(str, "!!%D", a);
+ snprint(str, sizeof(str), "!!%D", a);
goto brk;
}
parsetextconst(a->offset);
if(textarg == 0) {
- sprint(str, "$%lld", textstksiz);
+ snprint(str, sizeof(str), "$%lld", textstksiz);
goto brk;
}
- sprint(str, "$%lld-%lld", textstksiz, textarg);
+ snprint(str, sizeof(str), "$%lld-%lld", textstksiz, textarg);
goto brk;
}
if(i >= D_INDIR) {
if(a->offset)
- sprint(str, "%lld(%R)", a->offset, i-D_INDIR);
+ snprint(str, sizeof(str), "%lld(%R)", a->offset, i-D_INDIR);
else
- sprint(str, "(%R)", i-D_INDIR);
+ snprint(str, sizeof(str), "(%R)", i-D_INDIR);
goto brk;
}
switch(i) {
default:
if(a->offset)
- sprint(str, "$%lld,%R", a->offset, i);
+ snprint(str, sizeof(str), "$%lld,%R", a->offset, i);
else
- sprint(str, "%R", i);
+ snprint(str, sizeof(str), "%R", i);
break;
case D_NONE:
@@ -137,70 +137,70 @@ Dconv(Fmt *fp)
case D_BRANCH:
if(bigP != P && bigP->pcond != P)
if(a->sym != S)
- sprint(str, "%llux+%s", bigP->pcond->pc,
+ snprint(str, sizeof(str), "%llux+%s", bigP->pcond->pc,
a->sym->name);
else
- sprint(str, "%llux", bigP->pcond->pc);
+ snprint(str, sizeof(str), "%llux", bigP->pcond->pc);
else
- sprint(str, "%lld(PC)", a->offset);
+ snprint(str, sizeof(str), "%lld(PC)", a->offset);
break;
case D_EXTERN:
if(a->sym) {
- sprint(str, "%s+%lld(SB)", a->sym->name, a->offset);
+ snprint(str, sizeof(str), "%s+%lld(SB)", a->sym->name, a->offset);
break;
}
- sprint(str, "!!noname!!+%lld(SB)", a->offset);
+ snprint(str, sizeof(str), "!!noname!!+%lld(SB)", a->offset);
break;
case D_STATIC:
if(a->sym) {
- sprint(str, "%s<%d>+%lld(SB)", a->sym->name,
+ snprint(str, sizeof(str), "%s<%d>+%lld(SB)", a->sym->name,
a->sym->version, a->offset);
break;
}
- sprint(str, "!!noname!!<999>+%lld(SB)", a->offset);
+ snprint(str, sizeof(str), "!!noname!!<999>+%lld(SB)", a->offset);
break;
case D_AUTO:
if(a->sym) {
- sprint(str, "%s+%lld(SP)", a->sym->name, a->offset);
+ snprint(str, sizeof(str), "%s+%lld(SP)", a->sym->name, a->offset);
break;
}
- sprint(str, "!!noname!!+%lld(SP)", a->offset);
+ snprint(str, sizeof(str), "!!noname!!+%lld(SP)", a->offset);
break;
case D_PARAM:
if(a->sym) {
- sprint(str, "%s+%lld(%s)", a->sym->name, a->offset, paramspace);
+ snprint(str, sizeof(str), "%s+%lld(%s)", a->sym->name, a->offset, paramspace);
break;
}
- sprint(str, "!!noname!!+%lld(%s)", a->offset, paramspace);
+ snprint(str, sizeof(str), "!!noname!!+%lld(%s)", a->offset, paramspace);
break;
case D_CONST:
- sprint(str, "$%lld", a->offset);
+ snprint(str, sizeof(str), "$%lld", a->offset);
break;
case D_FCONST:
- sprint(str, "$(%.8lux,%.8lux)", a->ieee.h, a->ieee.l);
+ snprint(str, sizeof(str), "$(%.8lux,%.8lux)", a->ieee.h, a->ieee.l);
break;
case D_SCONST:
- sprint(str, "$\"%S\"", a->scon);
+ snprint(str, sizeof(str), "$\"%S\"", a->scon);
break;
case D_ADDR:
a->type = a->index;
a->index = D_NONE;
- sprint(str, "$%D", a);
+ snprint(str, sizeof(str), "$%D", a);
a->index = a->type;
a->type = D_ADDR;
goto conv;
}
brk:
if(a->index != D_NONE) {
- sprint(s, "(%R*%d)", a->index, a->scale);
+ snprint(s, sizeof(s), "(%R*%d)", a->index, a->scale);
strcat(str, s);
}
conv:
@@ -342,9 +342,9 @@ Rconv(Fmt *fp)
r = va_arg(fp->args, int);
if(r >= D_AL && r <= D_NONE)
- sprint(str, "%s", regstr[r-D_AL]);
+ snprint(str, sizeof(str), "%s", regstr[r-D_AL]);
else
- sprint(str, "gok(%d)", r);
+ snprint(str, sizeof(str), "gok(%d)", r);
return fmtstrcpy(fp, str);
}
コアとなるコードの解説
このコミットの核心は、sprint
関数を snprint
関数に置き換えることで、文字列フォーマット処理におけるバッファオーバーフローの脆弱性を排除した点にあります。
src/cmd/6l/list.c
ファイルは、Go言語のリンカ 6l
の一部であり、主にリンカのデバッグ出力やリスト表示に関連する機能を提供しています。このファイルには、Pconv
や Dconv
といった関数が含まれており、これらはリンカが処理するプログラムの命令(Prog
)やデータ(Addr
)構造体を、人間が読める形式の文字列に変換する役割を担っています。
変更前は、これらの変換処理において sprint
関数が使用されていました。sprint
は、C標準ライブラリの sprintf
と同様に、出力先のバッファサイズを考慮せずに文字列を書き込むため、フォーマットされる文字列がバッファの容量を超えた場合に、隣接するメモリ領域を上書きしてしまう可能性がありました。これは、プログラムのクラッシュや、悪意のあるコード実行につながるセキュリティ上の深刻な問題です。
変更後、すべての sprint
の呼び出しは snprint
に置き換えられ、第二引数として sizeof(str)
または sizeof(str1)
が追加されています。ここで str
や str1
は、フォーマットされた文字列を格納するための固定サイズの文字配列(バッファ)です。sizeof()
演算子は、このバッファの合計サイズをバイト単位で返します。
snprint
関数は、この sizeof()
で指定されたサイズを超えてバッファに書き込むことを防ぎます。これにより、たとえフォーマットされる文字列が非常に長くなったとしても、バッファオーバーフローが発生するリスクがなくなります。これは、リンカのようなシステムレベルのツールにおいて、堅牢性とセキュリティを確保するために非常に重要な変更です。
この修正は、Go言語の初期段階からセキュリティと安定性への配慮がなされていたことを示しており、後のGo言語の設計思想にも通じるものです。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語の初期の歴史に関する情報(Goの設計思想や初期のツールチェインについて理解を深めるのに役立つ可能性があります)
参考にした情報源リンク
- C言語の
sprintf
とsnprintf
に関するドキュメントやチュートリアル(例: cppreference.com, man pages) - バッファオーバーフローに関するセキュリティ情報
- Go言語のツールチェインの構造に関するドキュメント(特に初期のGoに関するもの)
- Go言語のリンカに関する技術記事や解説